diff --git a/functions.lua b/functions.lua index 93d7441..f4c4228 100644 --- a/functions.lua +++ b/functions.lua @@ -32,6 +32,18 @@ local protection_check = function(pos, player_name) return false end +local function deep_copy(table_in) + local table_out = {} + for index, value in pairs(table_in) do + if type(value) == "table" then + table_out[index] = deep_copy(value) + else + table_out[index] = value + end + end + return table_out +end + -------------------------------------------------------------------------------------- local create_new_id = function() @@ -126,6 +138,33 @@ local persist_step, retrieve_step = get_table_functions("step") -- actually just ------------------------------------------------------------------------------------------------------- -- Layout creation helpers +digtron.duplicate = function(digtron_id) + local layout = retrieve_layout(digtron_id) + if layout == nil then + minetest.log("error", "[Digtron] digtron.duplicate called with non-existent id " .. digtron_id) + return + end + local new_layout = deep_copy(layout) -- make a copy because persist_layout caches its parameter as-is + local new_id = create_new_id() + local new_name = S("Copy of @1", get_name(digtron_id)) + persist_layout(new_id, new_layout) + set_name(new_id, new_name) + + local old_inv = retrieve_inventory(digtron_id) + local new_inv = retrieve_inventory(new_id) + for inv_name, item_list in pairs(old_inv:get_lists()) do + -- Don't copy inventory contents, just copy sizes + new_inv:set_size(inv_name, #item_list) + end + persist_inventory(new_id) + + local new_controller = ItemStack("digtron:controller") + local meta = new_controller:get_meta() + meta:set_string("digtron_id", new_id) + meta:set_string("description", new_name) + return new_controller +end + -- recursive function searches out all connected unassigned digtron nodes local get_all_digtron_nodes get_all_digtron_nodes = function(pos, digtron_nodes, digtron_adjacent, player_name) @@ -346,7 +385,6 @@ local assemble = function(root_pos, player_name) return nil end -- Process inventories specially - -- fuel and main get added to corresponding detached inventory lists for listname, items in pairs(current_meta_table.inventory) do local count = #items -- increase the corresponding detached inventory size @@ -651,18 +689,6 @@ end ------------------------------------------------------------------------ -- Rotation -local function deep_copy(table_in) - local table_out = {} - for index, value in pairs(table_in) do - if type(value) == "table" then - table_out[index] = deep_copy(value) - else - table_out[index] = value - end - end - return table_out -end - local rotate_layout = function(digtron_id, axis) local layout = retrieve_layout(digtron_id) local axis_hash = minetest.hash_node_position(axis) @@ -1278,6 +1304,8 @@ digtron.get_sequence = retrieve_sequence digtron.set_step = persist_step digtron.get_step = retrieve_step +-- Used by duplicator +digtron.get_layout = retrieve_layout digtron.assemble = assemble digtron.disassemble = disassemble diff --git a/init.lua b/init.lua index 83c6cf3..35f150a 100644 --- a/init.lua +++ b/init.lua @@ -51,6 +51,7 @@ dofile(modpath.."/nodes/node_misc.lua") dofile(modpath.."/nodes/node_storage.lua") dofile(modpath.."/nodes/node_digger.lua") dofile(modpath.."/nodes/node_builder.lua") +dofile(modpath.."/nodes/node_duplicator.lua") dofile(modpath.."/nodes/recipes.lua") diff --git a/nodes/node_duplicator.lua b/nodes/node_duplicator.lua new file mode 100644 index 0000000..7034c24 --- /dev/null +++ b/nodes/node_duplicator.lua @@ -0,0 +1,284 @@ +-- internationalization boilerplate +local MP = minetest.get_modpath(minetest.get_current_modname()) +local S, NS = dofile(MP.."/intllib.lua") + +local strip_digtron_prefix = function(desc) + -- Having "Digtron " prefixing every description is annoying + local prefix = "Digtron " + if desc:sub(1,#prefix) == prefix then + desc = desc:sub(#prefix+1) + end + return desc +end + +local get_manifest = function(pos) + local manifest = {} + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + local template = inv:get_stack("template", 1) + local stack_meta = template:get_meta() + local digtron_id = stack_meta:get_string("digtron_id") + if digtron_id ~= "" then + local layout = digtron.get_layout(digtron_id) + for hash, data in pairs(layout) do + local item = data.node.name + local item_def = minetest.registered_items[item] + if item_def._digtron_disassembled_node then + item = item_def._digtron_disassembled_node + item_def = minetest.registered_items[item] + end + local desc = strip_digtron_prefix(item_def.description) + local entry = manifest[desc] + if entry == nil then + entry = {item = item} + manifest[desc] = entry + elseif entry.item ~= item then + minetest.log("error", "[Digtron] Duplicator found two digtron nodes that are defined with the same description: " + .. item .. " and " .. entry.item .. ". File an issue with Digtron's maintainers.") + end + entry.requires = (entry.requires or 0) + 1 + end + end + + local main_list = inv:get_list("main") + for _, itemstack in ipairs(main_list) do + if not itemstack:is_empty() then + local desc = strip_digtron_prefix(itemstack:get_definition().description) + local entry = manifest[desc] + if entry == nil then + entry = {item = item} + manifest[desc] = entry + end + entry.contains = (entry.contains or 0) + itemstack:get_count() + end + end + + local ok = false + if digtron_id ~= "" then + ok = true + for item, entry in pairs(manifest) do + if entry.requires and (entry.contains == nil or entry.contains < entry.requires) then + ok = false + break + end + end + end + + return manifest, ok, digtron_id +end + +local cache = {} + +local get_formspec = function(pos) + local hash = minetest.hash_node_position(pos) + local cache_val = cache[hash] + if cache_val == nil then + cache_val = {} + cache_val.manifest, cache_val.ok = get_manifest(pos) + cache[hash] = cache_val + end + local manifest = cache_val.manifest + local ok = cache_val.ok + + -- Build item table + local manifest_formspec_head = "tablecolumns[color;text,tooltip=" .. S("Digtron component.") + ..";text,align=center,tooltip=" .. S("Amount of this component required to copy the template Digtron.") + ..";text,align=center,tooltip=" .. S("Amount of this component currently available.") + .."]table[0,0;2.9,3;manifest;#FFFFFF,Item,Required,Available" + local manifest_formspec_body = {} + for desc, entry in pairs(manifest) do + local color = "#FFFFFF" + if entry.requires then + if entry.contains == nil or entry.contains < entry.requires then + color = "#FF0000" + else + color = "#00FF00" + end + end + manifest_formspec_body[#manifest_formspec_body + 1] = + ","..color..","..desc..","..(entry.requires or "-")..","..(entry.contains or "-") + end + table.sort(manifest_formspec_body) + local manifest_formspec_tail = ";]" + local manifest_formspec = manifest_formspec_head .. table.concat(manifest_formspec_body) .. manifest_formspec_tail + + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + + -- Duplicate button + local duplicate_button + if ok and inv:is_empty("copy") then + duplicate_button = "button[1,0;1,1;duplicate;"..S("Duplicate").."]tooltip[duplicate;" + .. S("Puts a copy of the template Digtron into the output inventory slot.") .. "]" + else + duplicate_button = "button[1,0;1,1;no_duplicate;X]tooltip[no_duplicate;" + .. S("Duplication cannot proceed at this time.") .. "]" + end + + return "size[8,9.3]" + + .. "container[5,1.5]" + .. manifest_formspec + .. "container_end[]" + + .."label[0,0;" .. S("Digtron components") .. "]" + .."list[current_name;main;0,0.6;5,4;]" + .."tooltip[0,0;5,4.6;".. S("Digtron components in this inventory will be used to create the duplicate.") .."]" + .."container[5,0]" + .."list[current_name;template;0,0;1,1;]" + .."tooltip[0,0;1,1.25;".. S("Place the Digtron you want to make a copy of here.") .."]" + .."label[0.1,0.8;" .. S("Template") .. "]" + ..duplicate_button + .."list[current_name;copy;2,0;1,1;]" + .."tooltip[2,0;1,1.25;".. S("The duplicate Digtron is output here.") .."]" + .."label[2.25,0.8;" .. S("Copy") .. "]" + .."container_end[]" + .."list[current_player;main;0,5.15;8,1;]" + .."list[current_player;main;0,6.38;8,3;8]" + .."listring[current_name;main]" + .."listring[current_player;main]" + ..default.get_hotbar_bg(0,5.15) +end + +minetest.register_node("digtron:duplicator", { + description = S("Digtron Duplicator"), + _doc_items_longdesc = digtron.doc.duplicator_longdesc, + _doc_items_usagehelp = digtron.doc.duplicator_usagehelp, + groups = {cracky = 3, oddly_breakable_by_hand=3}, + sounds = digtron.metal_sounds, + tiles = {"digtron_plate.png^(digtron_axel_side.png^[transformR90)", + "digtron_plate.png^(digtron_axel_side.png^[transformR270)", + "digtron_plate.png^digtron_axel_side.png", + "digtron_plate.png^(digtron_axel_side.png^[transformR180)", + "digtron_plate.png^digtron_builder.png", + "digtron_plate.png", + }, + paramtype = "light", + paramtype2= "facedir", + is_ground_content = false, + drawtype="nodebox", + node_box = { + type = "fixed", + fixed = { + {-0.5, 0.3125, 0.3125, 0.5, 0.5, 0.5}, -- FrontFrame_top + {-0.5, -0.5, 0.3125, 0.5, -0.3125, 0.5}, -- FrontFrame_bottom + {0.3125, -0.3125, 0.3125, 0.5, 0.3125, 0.5}, -- FrontFrame_right + {-0.5, -0.3125, 0.3125, -0.3125, 0.3125, 0.5}, -- FrontFrame_left + {-0.0625, -0.3125, 0.3125, 0.0625, 0.3125, 0.375}, -- frontcross_vertical + {-0.3125, -0.0625, 0.3125, 0.3125, 0.0625, 0.375}, -- frontcross_horizontal + {-0.4375, -0.4375, -0.4375, 0.4375, 0.4375, 0.3125}, -- Body + {-0.5, -0.3125, -0.5, -0.3125, 0.3125, -0.3125}, -- backframe_vertical + {0.3125, -0.3125, -0.5, 0.5, 0.3125, -0.3125}, -- backframe_left + {-0.5, 0.3125, -0.5, 0.5, 0.5, -0.3125}, -- backframe_top + {-0.5, -0.5, -0.5, 0.5, -0.3125, -0.3125}, -- backframe_bottom + {-0.0625, -0.0625, -0.5625, 0.0625, 0.0625, -0.4375}, -- back_probe + }, + }, + selection_box = { + type = "regular" + }, + + on_construct = function(pos) + local meta = minetest.get_meta(pos) + meta:set_string("formspec", get_formspec(pos)) + local inv = meta:get_inventory() + inv:set_size("main", 5*4) + inv:set_size("template", 1) + inv:set_size("copy", 1) + end, + + on_destruct = function(pos) + cache[minetest.hash_node_position(pos)] = nil + end, + + can_dig = function(pos,player) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + return inv:is_empty("main") and inv:is_empty("template") and inv:is_empty("copy") + end, + + allow_metadata_inventory_put = function(pos, listname, index, stack, player) + local stack_name = stack:get_name() + if listname == "main" and (minetest.get_item_group(stack_name, "digtron") > 0 or stack_name == "digtron:controller_unassembled") then + return stack:get_count() + elseif listname == "template" and stack:get_name() == "digtron:controller" then + return stack:get_count() + end + return 0 + end, + + allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player) + if from_list == to_list then + return count + end + return 0 + end, + + allow_metadata_inventory_take = function(pos, listname, index, stack, player) + return stack:get_count() + end, + + on_metadata_inventory_put = function(pos, listname, index, stack, player) + local meta = minetest.get_meta(pos) + cache[minetest.hash_node_position(pos)] = nil + meta:set_string("formspec", get_formspec(pos)) + end, + on_metadata_inventory_take = function(pos, listname, index, stack, player) + local meta = minetest.get_meta(pos) + cache[minetest.hash_node_position(pos)] = nil + meta:set_string("formspec", get_formspec(pos)) + end, + + on_receive_fields = function(pos, formname, fields, sender) + if fields.help then + minetest.after(0.5, doc.show_entry, sender:get_player_name(), "nodes", "digtron:duplicator", true) + end + + if fields.no_duplicate then + minetest.sound_play("digtron_error", {gain=0.5, to_player=sender:get_player_name()}) -- Insufficient inventory + end + + if fields.duplicate then + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + + if not inv:is_empty("copy") then + minetest.log("error", "[Digtron] duplicator was sent a 'duplicate' command by " .. player_name + .. "but there was an item in the output inventory already. This should be impossible.") + minetest.sound_play("digtron_error", {gain=0.5, to_player=sender:get_player_name()}) + return + end + + local manifest, ok, digtron_id = get_manifest(pos) -- don't trust formspec fields, that's hackable. Recalculate manifest. + if not ok then + local player_name = sender:get_player_name() + minetest.log("error", "[Digtron] duplicator was sent a 'duplicate' command by " .. player_name + .. "but get_manifest reported insufficent inputs. This should be impossible.") + minetest.sound_play("digtron_error", {gain=0.5, to_player=player_name}) + return + end + + -- deduct nodes from duplicator inventory + for desc, entry in pairs(manifest) do + if entry.requires then + local count = entry.requires + while count > 0 do + -- We need to do this loop because we may be wanting to remove more items than + -- a single stack of that item can hold. + -- https://github.com/minetest/minetest/issues/8883 + local stack_to_remove = ItemStack({name=entry.item, count=count}) + stack_to_remove:set_count(math.min(count, stack_to_remove:get_stack_max())) + local removed = inv:remove_item("main", stack_to_remove) + count = count - removed:get_count() + end + end + end + + local new_digtron = digtron.duplicate(digtron_id) + inv:set_stack("copy", 1, new_digtron) + + minetest.sound_play("digtron_machine_assemble", {gain=1.0, pos=pos}) + end + end, + +}) diff --git a/nodes/recipes.lua b/nodes/recipes.lua index 91274a5..8ea09d7 100644 --- a/nodes/recipes.lua +++ b/nodes/recipes.lua @@ -111,14 +111,14 @@ minetest.register_craft({ } }) ---minetest.register_craft({ --- output = "digtron:duplicator", --- recipe = { --- {"default:mese_crystal","default:mese_crystal","default:mese_crystal"}, --- {"default:chest","digtron:digtron_core","default:chest"}, --- {"default:mese_crystal","default:mese_crystal","default:mese_crystal"} --- } ---}) +minetest.register_craft({ + output = "digtron:duplicator", + recipe = { + {"default:mese_crystal","default:mese_crystal","default:mese_crystal"}, + {"default:chest","digtron:digtron_core","default:chest"}, + {"default:mese_crystal","default:mese_crystal","default:mese_crystal"} + } +}) --minetest.register_craft({ -- output = "digtron:inventory_ejector", @@ -174,17 +174,20 @@ minetest.register_craft({ type = "shapeless", recipe = {"digtron:soft_digger_static", "digtron:soft_digger_static"}, }) + minetest.register_craft({ output = "digtron:dual_digger_static", type = "shapeless", recipe = {"digtron:digger_static", "digtron:digger_static"}, }) + minetest.register_craft({ output = "digtron:soft_digger_static 2", recipe = { {"digtron:dual_soft_digger_static"}, } }) + minetest.register_craft({ output = "digtron:digger_static 2", recipe = { @@ -200,18 +203,21 @@ minetest.register_craft({ {"digtron:structure"}, } }) + minetest.register_craft({ output = "digtron:digtron_core", recipe = { {"digtron:panel"}, } }) + minetest.register_craft({ output = "digtron:digtron_core", recipe = { {"digtron:corner_panel"}, } }) + minetest.register_craft({ output = "digtron:digtron_core", recipe = {