implement duplicator

This commit is contained in:
FaceDeer 2019-09-02 14:28:13 -06:00
parent 1fa10d81ba
commit 63a7f6aaaa
4 changed files with 340 additions and 21 deletions

@ -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

@ -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")

284
nodes/node_duplicator.lua Normal file

@ -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,
})

@ -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 = {