From 6fb3ded8ad12fe4dc887452806fe5e2a7704e945 Mon Sep 17 00:00:00 2001 From: Buckaroo Banzai <39065740+BuckarooBanzay@users.noreply.github.com> Date: Fri, 7 Feb 2025 15:47:20 +0100 Subject: [PATCH] Add access cards (#88) * add access cards * colored cards * rename / recipes * impl * crafting fix * indent fixes * door groups fix (should not be diggable by hand) --------- Co-authored-by: BuckarooBanzay --- access_card.lua | 254 +++++++++++++++++++++++++++ doors.lua | 20 ++- init.lua | 1 + textures/scifi_nodes_access_card.png | Bin 0 -> 4871 bytes 4 files changed, 267 insertions(+), 8 deletions(-) create mode 100644 access_card.lua create mode 100644 textures/scifi_nodes_access_card.png diff --git a/access_card.lua b/access_card.lua new file mode 100644 index 0000000..4aadc1b --- /dev/null +++ b/access_card.lua @@ -0,0 +1,254 @@ +local FORMSPEC_NAME = "scifi_nodes:access_card_configure" + +local function create_id() + local template = "xxxxxx" + return string.gsub(template, '[x]', function () + return string.format('%x', math.random(0, 0xf)) + end) +end + +local function get_door_access_table(meta) + local str = meta:get_string("access") + if str == "" then + -- no config + return {} + else + return minetest.deserialize(str) or {} + end +end + +local function set_door_access_table(meta, at) + if next(at) then + meta:set_string("access", minetest.serialize(at)) + else + meta:set_string("access", "") + end +end + +function scifi_nodes.door_check_access_card(node_pos, itemstack, player) + local node_meta = minetest.get_meta(node_pos) + local access_table = get_door_access_table(node_meta) + if not next(access_table) then + -- access not restricted + return true + end + + local playername = player:get_player_name() + + if itemstack:get_name() ~= "scifi_nodes:access_card" then + minetest.chat_send_player(playername, minetest.colorize("#ff0000", "Access denied: no access card detected!")) + minetest.sound_play("scifi_nodes_scanner_refused", { pos = node_pos, max_hear_distance = 10 }) + return false + end + + local item_meta = itemstack:get_meta() + local id = item_meta:get_string("id") + if id == "" then + minetest.chat_send_player(playername, minetest.colorize("#ff0000", "Access denied: unconfigured access card")) + minetest.sound_play("scifi_nodes_scanner_refused", { pos = node_pos, max_hear_distance = 10 }) + return false + end + + if not access_table[id] then + minetest.chat_send_player(playername, minetest.colorize("#ff0000", "Access denied: invalid access card")) + minetest.sound_play("scifi_nodes_scanner_refused", { pos = node_pos, max_hear_distance = 10 }) + return false + end + + minetest.sound_play("scifi_nodes_scanner_granted", { pos = node_pos, max_hear_distance = 10 }) + return true +end + +-- using a card against a door toggles the access to it +local function on_use(itemstack, player, pointed_thing) + local playername = player:get_player_name() + local pos = pointed_thing.under + + if not pos then + -- nothing selected + return + end + + if minetest.is_protected(pos, playername) then + -- protected + return + end + + local node = minetest.get_node(pos) + local node_def = minetest.registered_nodes[node.name] + if not node_def.groups or not node_def.groups.scifi_nodes_door then + -- incompatible node + return + end + + local item_meta = itemstack:get_meta() + local card_id = item_meta:get_string("id") + if card_id == "" then + minetest.chat_send_player(playername, "Access card is unconfigured") + return + end + local card_name = item_meta:get_string("name") + + local node_meta = minetest.get_meta(pos) + local access_table = get_door_access_table(node_meta) + + if access_table[card_id] then + -- remove access + access_table[card_id] = nil + minetest.chat_send_player(playername, "Revoked access to card-id '" .. card_id .. "'") + minetest.sound_play("scifi_nodes_scanner_refused", { pos = pos, max_hear_distance = 10 }) + else + -- grant access + access_table[card_id] = card_name + minetest.chat_send_player(playername, "Granted access to card-id '" .. card_id .. "'") + minetest.sound_play("scifi_nodes_scanner_granted", { pos = pos, max_hear_distance = 10 }) + end + + local msg = "" + if next(access_table) then + msg = msg .. "Current registered cards: " + for id, name in pairs(access_table) do + msg = msg .. "Name: '" .. name .. "' ID: '" .. id .. "' / " + end + else + -- access table is empty + msg = "No access-card left in access-table, door is free to use" + end + minetest.chat_send_player(playername, msg) + + -- set new access table + set_door_access_table(node_meta, access_table) +end + +local function configure_access_card(meta, name) + local id = meta:get_string("id") + if id == "" then + -- set new id + id = create_id() + meta:set_string("id", id) + end + + meta:set_string("name", name) + meta:set_string("description", "Access card '" .. name .. "' (id: '" .. id .. "')") +end + +-- right-click with a card for rename/configuration +local function on_secondary_use(itemstack, player, pointed_thing) + local meta = itemstack:get_meta() + if meta:get_string("id") == "" then + -- initial configuration + configure_access_card(meta, "") + end + local name = meta:get_string("name") + + minetest.show_formspec(player:get_player_name(), FORMSPEC_NAME, [[ + size[10,1.4] + real_coordinates[true] + field[0.1,0.4;7,0.8;name;Name;]] .. minetest.formspec_escape(name) .. [[] + button_exit[7.3,0.4;2.5,0.8;save;Save] + ]]) + + return itemstack +end + +minetest.register_on_player_receive_fields(function(player, formname, fields) + if formname ~= FORMSPEC_NAME then + -- wrong formspec + return false + end + + if not fields.save and not fields.key_enter_field then + -- quit + return true + end + + local itemstack = player:get_wielded_item() + if itemstack:get_name() ~= "scifi_nodes:access_card" then + -- invalid item + return true + end + + local meta = itemstack:get_meta() + configure_access_card(meta, fields.name or "") + player:set_wielded_item(itemstack) +end) + +minetest.register_craftitem("scifi_nodes:access_card", { + description = "Access card (unconfigured)", + inventory_image = "scifi_nodes_access_card.png", + palette = "unifieddyes_palette_extended.png", + stack_max = 1, + paramtype2 = "color", + on_use = on_use, + on_secondary_use = on_secondary_use, + groups = { + ud_param2_colorable = 1 + } +}) + +-- initial recipe +minetest.register_craft({ + output = "scifi_nodes:access_card", + recipe = { + {"scifi_nodes:white2", "", ""}, + {"scifi_nodes:white_pad", "", ""}, + {"scifi_nodes:white2", "", ""} + } +}) + +-- copy recipe +minetest.register_craft({ + output = "scifi_nodes:access_card", + recipe = { + {"scifi_nodes:access_card", "scifi_nodes:white_pad", ""}, + {"", "", ""}, + {"", "", ""} + } +}) + +minetest.register_on_craft(function(itemstack, player, old_craft_grid, craft_inv) + if itemstack:get_name() ~= "scifi_nodes:access_card" then + return + end + + local original + local index + for i = 1, #old_craft_grid do + if old_craft_grid[i]:get_name() == "scifi_nodes:access_card" then + original = old_craft_grid[i] + index = i + break + end + end + if not original then + return + end + + -- copy metadata + local src_meta = original:get_meta() + local dst_meta = itemstack:get_meta() + local copy_fields = {"id", "name", "description"} + for _, fieldname in ipairs(copy_fields) do + dst_meta:set_string(fieldname, src_meta:get_string(fieldname)) + end + + if old_craft_grid[2]:get_name() == "scifi_nodes:white_pad" then + -- keep original item if the copy-recipe is used + craft_inv:set_stack("craft", index, original) + end +end) + +if minetest.get_modpath("unifieddyes") then + -- add colored crafts + unifieddyes.register_color_craft({ + output = "scifi_nodes:access_card", + palette = "extended", + neutral_node = "scifi_nodes:access_card", + recipe = { + "NEUTRAL_NODE", + "MAIN_DYE" + }, + type = "shapeless" + }) + +end \ No newline at end of file diff --git a/doors.lua b/doors.lua index 8f317d6..f9bdaf9 100644 --- a/doors.lua +++ b/doors.lua @@ -148,14 +148,18 @@ for _, current_door in ipairs(doors) do local adjacent = minetest.get_node({x=x, y=y, z=z}) if adjacent.name == target_opposite then - minetest.set_node({x=x, y=y, z=z}, {name=target, param2 = adjacent.param2}) - minetest.set_node({x=x, y=y+1, z=z}, {name=target_top, param2 = adjacent.param2}) + minetest.swap_node({x=x, y=y, z=z}, {name=target, param2 = adjacent.param2}) + minetest.swap_node({x=x, y=y+1, z=z}, {name=target_top, param2 = adjacent.param2}) end end end - local function open_door(pos, node) + local function open_door(pos, node, player, itemstack) + if not scifi_nodes.door_check_access_card(pos, itemstack, player) then + return + end + -- play sound minetest.sound_play(sound,{ max_hear_distance = 16, @@ -165,8 +169,8 @@ for _, current_door in ipairs(doors) do local timer = minetest.get_node_timer(pos) - minetest.set_node(pos, {name=opened, param2=node.param2}) - minetest.set_node({x=pos.x,y=pos.y+1,z=pos.z}, {name=opened_top, param2=node.param2}) + minetest.swap_node(pos, {name=opened, param2=node.param2}) + minetest.swap_node({x=pos.x,y=pos.y+1,z=pos.z}, {name=opened_top, param2=node.param2}) change_adjacent(opened, pos, node) @@ -188,8 +192,8 @@ for _, current_door in ipairs(doors) do local node = minetest.get_node(pos) - minetest.set_node(pos, {name=closed, param2=node.param2}) - minetest.set_node({x=pos.x,y=pos.y+1,z=pos.z}, {name=closed_top, param2=node.param2}) + minetest.swap_node(pos, {name=closed, param2=node.param2}) + minetest.swap_node({x=pos.x,y=pos.y+1,z=pos.z}, {name=closed_top, param2=node.param2}) change_adjacent(closed, pos, node) end @@ -242,7 +246,7 @@ for _, current_door in ipairs(doors) do paramtype2 = "facedir", groups = { cracky = 3, - oddly_breakable_by_hand = 1, + dig_generic = 3, scifi_nodes_door = 1, door = 1 }, diff --git a/init.lua b/init.lua index eec022a..4603e73 100644 --- a/init.lua +++ b/init.lua @@ -24,6 +24,7 @@ dofile(MP.."/ambience.lua") dofile(MP.."/plants.lua") dofile(MP.."/models.lua") dofile(MP.."/nodes.lua") +dofile(MP.."/access_card.lua") dofile(MP.."/doors.lua") dofile(MP.."/switch.lua") dofile(MP.."/protected_switch.lua") diff --git a/textures/scifi_nodes_access_card.png b/textures/scifi_nodes_access_card.png new file mode 100644 index 0000000000000000000000000000000000000000..6810e52d5753516cf164f83045664918cff396a4 GIT binary patch literal 4871 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 literal 0 HcmV?d00001