hide digtron_id from clients, change "construct" to "assemble", various other tidbits

This commit is contained in:
FaceDeer 2019-08-20 00:29:26 -06:00
parent 03c9a81b6b
commit 2283cdbbea
3 changed files with 161 additions and 112 deletions

@ -2,6 +2,11 @@
local MP = minetest.get_modpath(minetest.get_current_modname())
local S, NS = dofile(MP.."/intllib.lua")
-- This allows us to know which digtron the player has a formspec open for without
-- sending the digtron_id over the network
local player_interacting_with_digtron_id = {}
local player_interacting_with_digtron_pos = {}
local controller_nodebox = {
type = "fixed",
fixed = {
@ -18,23 +23,24 @@ local controller_nodebox = {
},
}
local get_controller_unconstructed_formspec = function(pos, player_name)
local get_controller_unassembled_formspec = function(pos, player_name)
local meta = minetest.get_meta(pos)
return "size[9,9]"
.. "container[0.5,0]"
.. "button[0,0;1,1;construct;Construct]"
.. "button[0,0;1,1;assemble;Assemble]"
.. "field[1.2,0.25;2,1;digtron_name;Digtron name;"..meta:get_string("infotext").."]"
.. "field_close_on_enter[digtron_name;false]"
.. "container_end[]"
end
local get_controller_constructed_formspec = function(pos, digtron_id, player_name)
local get_controller_assembled_formspec = function(pos, digtron_id, player_name)
digtron.retrieve_inventory(digtron_id) -- ensures the detatched inventory exists and is populated
return "size[9,9]"
.. "container[0.5,0]"
.. "button[0,0;1,1;deconstruct;Deconstruct]"
.. "button[0,0;1,1;disassemble;Disassemble]"
.. "field[1.2,0.25;2,1;digtron_name;Digtron name;"..digtron.get_name(digtron_id).."]"
.. "field_close_on_enter[digtron_name;false]"
.. "button[3,0;1,1;move_forward;Move forward]"
.. "container_end[]"
.. "container[0.5,1]"
.. "list[detached:" .. digtron_id .. ";main;0,0;8,2]" -- TODO: paging system for inventory, guard against non-existent listname
@ -141,23 +147,26 @@ minetest.register_node("digtron:controller", {
meta:set_string("infotext", title)
meta:set_string("digtron_id", digtron_id)
meta:mark_as_private("digtron_id")
end,
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
if clicker == nil then return end
local meta = minetest.get_meta(pos)
local digtron_id = meta:get_string("digtron_id")
local player_name
if clicker then player_name = clicker:get_player_name() end
local player_name = clicker:get_player_name()
if digtron_id == "" then
player_interacting_with_digtron_pos[player_name] = pos
minetest.show_formspec(player_name,
"digtron_controller_unconstructed:"..minetest.pos_to_string(pos)..":"..player_name,
get_controller_unconstructed_formspec(pos, player_name))
"digtron:controller_unassembled",
get_controller_unassembled_formspec(pos, player_name))
else
-- initialized
player_interacting_with_digtron_id[player_name] = digtron_id
minetest.show_formspec(player_name,
"digtron_controller_constructed:"..minetest.pos_to_string(pos)..":"..player_name..":"..digtron_id,
get_controller_constructed_formspec(pos, digtron_id, player_name))
"digtron:controller_assembled",
get_controller_assembled_formspec(pos, digtron_id, player_name))
end
end,
@ -167,30 +176,26 @@ minetest.register_node("digtron:controller", {
on_blast = digtron.on_blast,
})
-- Dealing with an unconstructed Digtron controller
-- Dealing with an unassembled Digtron controller
minetest.register_on_player_receive_fields(function(player, formname, fields)
local formname_split = formname:split(":")
if #formname_split ~= 3 or formname_split[1] ~= "digtron_controller_unconstructed" then
return
end
local pos = minetest.string_to_pos(formname_split[2])
if pos == nil then
minetest.log("error", "[Digtron] Unable to parse position from formspec name " .. formname)
return
end
local name = formname_split[3]
if player:get_player_name() ~= name then
if formname ~= "digtron:controller_unassembled" then
return
end
local name = player:get_player_name()
local pos = player_interacting_with_digtron_pos[name]
if pos == nil then return end
if fields.construct then
local digtron_id = digtron.construct(pos, name)
if fields.assemble then
local digtron_id = digtron.assemble(pos, name)
if digtron_id then
local meta = minetest.get_meta(pos)
meta:set_string("digtron_id", digtron_id)
meta:mark_as_private("digtron_id")
player_interacting_with_digtron_id[name] = digtron_id
minetest.show_formspec(name,
"digtron_controller_constructed:"..minetest.pos_to_string(pos)..":"..name..":"..digtron_id,
get_controller_constructed_formspec(pos, digtron_id, name))
"digtron:controller_assembled",
get_controller_assembled_formspec(pos, digtron_id, name))
end
end
@ -203,39 +208,30 @@ end)
-- Controlling a fully armed and operational Digtron
minetest.register_on_player_receive_fields(function(player, formname, fields)
local formname_split = formname:split(":")
if #formname_split ~= 4 or formname_split[1] ~= "digtron_controller_constructed" then
if formname ~= "digtron:controller_assembled" then
return
end
local pos = minetest.string_to_pos(formname_split[2])
if pos == nil then
minetest.log("error", "[Digtron] Unable to parse position from formspec name " .. formname)
return
end
local name = formname_split[3]
if player:get_player_name() ~= name then
return
end
local digtron_id = formname_split[4]
local name = player:get_player_name()
local digtron_id = player_interacting_with_digtron_id[name]
if digtron_id == nil then return end
if fields.deconstruct then
minetest.chat_send_all("Deconstructing " .. digtron_id)
local meta = minetest.get_meta(pos)
local digtron_id = meta:get_string("digtron_id")
if digtron_id == "" then
minetest.log("error", "[Digtron] tried to deconstruct Digtron at pos "
.. minetest.pos_to_string(pos) .. " but it had no digtron_id in the node's metadata")
else
digtron.deconstruct(digtron_id, pos, name)
if fields.disassemble then
local pos = digtron.disassemble(digtron_id, name)
if pos then
player_interacting_with_digtron_pos[name] = pos
minetest.show_formspec(name,
"digtron_controller_unconstructed:"..minetest.pos_to_string(pos)..":"..name,
get_controller_unconstructed_formspec(pos, name))
"digtron:controller_unassembled",
get_controller_unassembled_formspec(pos, name))
end
end
if fields.move_forward then
end
--TODO: this isn't recording the field when using ESC to exit the formspec
if fields.key_enter_field == "digtron_name" or fields.digtron_name then
local pos = digtron.get_pos(digtron_id)
local meta = minetest.get_meta(pos)
meta:set_string("infotext", fields.digtron_name)
digtron.set_name(digtron_id, fields.digtron_name)

@ -1,2 +1,3 @@
default
intllib?
intllib?
creative?

@ -3,6 +3,7 @@ local mod_meta = minetest.get_mod_storage()
digtron.layout = {}
digtron.adjacent = {}
digtron.bounding_box = {}
digtron.pos = {}
--minetest.debug(dump(mod_meta:to_table()))
@ -120,17 +121,17 @@ end)
--------------------------------------------------------------------------------------
local create_new_id = function()
local last_id = mod_meta:get_int("last_id") -- returns 0 when uninitialized, so 0 will never be a valid digtron_id.
local new_id = last_id + 1
mod_meta:set_int("last_id", new_id) -- ensure each call to this method gets a unique number
local digtron_id = "digtron" .. tostring(new_id)
local digtron_id = "digtron" .. tostring(math.random(1, 2^21)) -- TODO: use SecureRandom()
-- It's super unlikely that we'll get a collision, but what the heck - maybe something will go
-- wrong with the random number source
while mod_meta:get_string(digtron_id..":layout") ~= "" do
digtron_id = "digtron" .. tostring(math.random(1, 2^21))
end
local inv = minetest.create_detached_inventory(digtron_id, detached_inventory_callbacks)
return digtron_id, inv
end
-- Deletes a Digtron record. Note: just throws everything away, this is not digtron.deconstruct.
-- Deletes a Digtron record. Note: just throws everything away, this is not digtron.disassemble.
local dispose_id = function(digtron_id)
minetest.remove_detached_inventory(digtron_id)
digtron.layout[digtron_id] = nil
@ -140,17 +141,22 @@ local dispose_id = function(digtron_id)
mod_meta:set_string(digtron_id..":adjacent", "")
mod_meta:set_string(digtron_id..":name", "")
mod_meta:set_string(digtron_id..":bounding_box", "")
mod_meta:set_string(digtron_id..":pos", "")
end
--------------------------------------------------------------------------------------------
-- Name
-- Not bothering with a dynamic table store for names, they're just strings with no need for serialization or deserialization
digtron.get_name = function(digtron_id)
return mod_meta:get_string(digtron_id..":name")
end
digtron.set_name = function(digtron_id, digtron_name)
mod_meta:set_string(digtron_id..":name", digtron_name)
-- Don't allow a name to be set for a non-existent Digtron
if mod_meta:get(digtron_id..":layout") then
mod_meta:set_string(digtron_id..":name", digtron_name)
end
end
-------------------------------------------------------------------------------------------------------
@ -186,6 +192,10 @@ local persist_adjacent = get_persist_table_function("adjacent")
local retrieve_adjacent = get_retrieve_table_function("adjacent")
local persist_bounding_box = get_persist_table_function("bounding_box")
local retrieve_bounding_box = get_retrieve_table_function("bounding_box")
local persist_pos = get_persist_table_function("pos")
local retrieve_pos = get_retrieve_table_function("pos")
digtron.get_pos = retrieve_pos
local cardinal_directions = {
{x=1,y=0,z=0},
@ -235,32 +245,32 @@ get_all_adjacent_digtron_nodes = function(pos, digtron_nodes, digtron_adjacent,
end
--------------------------------------------------------------------------------------------------------
-- Construct and deconstruct
-- assemble and disassemble
local origin_hash = minetest.hash_node_position({x=0,y=0,z=0})
-- Returns the id of the new Digtron record, or nil on failure
digtron.construct = function(root_pos, player_name)
digtron.assemble = function(root_pos, player_name)
local node = minetest.get_node(root_pos)
-- TODO: a more generic test? Not needed with the more generic controller design, as far as I can tell. There's only going to be the one type of controller.
if node.name ~= "digtron:controller" then
-- Called on an incorrect node
minetest.log("error", "[Digtron] digtron.construct called with pos " .. minetest.pos_to_string(root_pos)
minetest.log("error", "[Digtron] digtron.assemble called with pos " .. minetest.pos_to_string(root_pos)
.. " but the node at this location was " .. node.name)
return nil
end
local root_meta = minetest.get_meta(root_pos)
if root_meta:contains("digtron_id") then
-- Already constructed. TODO: validate that the digtron_id actually exists as well
minetest.log("error", "[Digtron] digtron.construct called with pos " .. minetest.pos_to_string(root_pos)
.. " but the controller at this location was already part of a constructed Digtron.")
-- Already assembled. TODO: validate that the digtron_id actually exists as well
minetest.log("error", "[Digtron] digtron.assemble called with pos " .. minetest.pos_to_string(root_pos)
.. " but the controller at this location was already part of a assembled Digtron.")
return nil
end
local root_hash = minetest.hash_node_position(root_pos)
local digtron_nodes = {[root_hash] = node} -- Nodes that are part of Digtron.
-- Initialize with the controller, it won't be added by get_all_adjacent_digtron_nodes
local digtron_adjacent = {} -- Nodes that are adjacent to Digtron but not a part of it
local bounding_box = {minp=vector.new(root_pos), maxp=vector.new(root_pos), root = root_pos}
local bounding_box = {minp=vector.new(root_pos), maxp=vector.new(root_pos)}
get_all_adjacent_digtron_nodes(root_pos, digtron_nodes, digtron_adjacent, bounding_box, player_name)
local digtron_id, digtron_inv = create_new_id(root_pos)
@ -280,7 +290,7 @@ digtron.construct = function(root_pos, player_name)
if current_meta_table.fields.digtron_id then
-- Trying to incorporate part of an existing digtron, should be impossible.
minetest.log("error", "[Digtron] digtron.construct tried to incorporate a Digtron node of type "
minetest.log("error", "[Digtron] digtron.assemble tried to incorporate a Digtron node of type "
.. node.name .. " at " .. minetest.pos_to_string(minetest.get_position_from_hash(hash))
.. " that was already assigned to digtron id " .. current_meta_table.fields.digtron_id)
dispose_id(digtron_id)
@ -313,17 +323,18 @@ digtron.construct = function(root_pos, player_name)
persist_layout(digtron_id, layout)
persist_adjacent(digtron_id, digtron_adjacent)
persist_bounding_box(digtron_id, bounding_box)
persist_pos(digtron_id, root_pos)
-- Wipe out the inventories of all in-world nodes, it's stored in the mod_meta now.
-- Wait until now to do it in case the above loop fails partway through.
for hash, node in pairs(digtron_nodes) do
local digtron_meta
local node_meta
if hash == root_hash then
digtron_meta = root_meta -- we're processing the controller, we already have a reference to its meta
node_meta = root_meta -- we're processing the controller, we already have a reference to its meta
else
digtron_meta = minetest.get_meta(minetest.get_position_from_hash(hash))
node_meta = minetest.get_meta(minetest.get_position_from_hash(hash))
end
local inv = digtron_meta:get_inventory()
local inv = node_meta:get_inventory()
for listname, items in pairs(inv:get_lists()) do
for i = 1, #items do
@ -331,10 +342,13 @@ digtron.construct = function(root_pos, player_name)
end
end
digtron_meta:set_string("digtron_id", digtron_id)
node_meta:set_string("digtron_id", digtron_id)
node_meta:mark_as_private("digtron_id")
end
minetest.log("action", "Digtron " .. digtron_id .. " assembled at " .. minetest.pos_to_string(root_pos)
.. " by " .. player_name)
minetest.debug("constructed id " .. digtron_id)
return digtron_id
end
@ -342,38 +356,41 @@ end
-- Returns pos, node, and meta for the digtron node provided the in-world node matches the layout
-- returns nil otherwise
local get_valid_data = function(digtron_id, root_hash, hash, data, function_name)
local ipos = minetest.get_position_from_hash(hash + root_hash - origin_hash)
local node = minetest.get_node(ipos)
local imeta = minetest.get_meta(ipos)
local target_digtron_id = imeta:get_string("digtron_id")
local node_pos = minetest.get_position_from_hash(hash + root_hash - origin_hash)
local node = minetest.get_node(node_pos)
local node_meta = minetest.get_meta(node_pos)
local target_digtron_id = node_meta:get_string("digtron_id")
if data.node.name ~= node.name then
minetest.log("error", "[Digtron] " .. function_name .. " tried interacting with one of ".. digtron_id .. "'s "
.. data.node.name .. "s at " .. minetest.pos_to_string(ipos) .. " but the node at that location was of type "
.. data.node.name .. "s at " .. minetest.pos_to_string(node_pos) .. " but the node at that location was of type "
.. node.name)
return
elseif target_digtron_id ~= digtron_id then
if target_digtron_id ~= "" then
minetest.log("error", "[Digtron] " .. function_name .. " tried interacting with ".. digtron_id .. "'s "
.. data.node.name .. " at " .. minetest.pos_to_string(ipos)
.. data.node.name .. " at " .. minetest.pos_to_string(node_pos)
.. " but the node at that location had a non-matching digtron_id value of \""
.. target_digtron_id .. "\"")
return
else
-- Allow digtron to recover from bad map metadata writes, the bane of Digtron 1.0's existence
minetest.log("warning", "[Digtron] " .. function_name .. " tried interacting with ".. digtron_id .. "'s "
.. data.node.name .. " at " .. minetest.pos_to_string(ipos)
.. data.node.name .. " at " .. minetest.pos_to_string(node_pos)
.. " but the node at that location had no digtron_id in its metadata. "
.. "Since the node type matched the layout, however, it was included anyway. It's possible "
.. "its metadata was not written correctly by a previous Digtron activity.")
return ipos, node, imeta
return node_pos, node, node_meta
end
end
return ipos, node, imeta
return node_pos, node, node_meta
end
-- Turns the Digtron back into pieces
digtron.deconstruct = function(digtron_id, root_pos, player_name)
digtron.disassemble = function(digtron_id, player_name)
local bbox = retrieve_bounding_box(digtron_id)
local root_pos = retrieve_pos(digtron_id)
local root_meta = minetest.get_meta(root_pos)
root_meta:set_string("infotext", digtron.get_name(digtron_id))
@ -381,8 +398,8 @@ digtron.deconstruct = function(digtron_id, root_pos, player_name)
local inv = digtron.retrieve_inventory(digtron_id)
if not (layout and inv) then
minetest.log("error", "digtron.deconstruct was unable to find either layout or inventory record for " .. digtron_id
.. ", deconstruction was impossible. Clearing any other remaining data for this id.")
minetest.log("error", "digtron.disassemble was unable to find either layout or inventory record for " .. digtron_id
.. ", disassembly was impossible. Clearing any other remaining data for this id.")
dispose_id(digtron_id)
return
end
@ -391,15 +408,15 @@ digtron.deconstruct = function(digtron_id, root_pos, player_name)
-- Write metadata and inventory to in-world node at this location
for hash, data in pairs(layout) do
local ipos, node, imeta = get_valid_data(digtron_id, root_hash, hash, data, "digtron.deconstruct")
local node_pos, node, node_meta = get_valid_data(digtron_id, root_hash, hash, data, "digtron.disassemble")
if ipos then
local iinv = imeta:get_inventory()
if node_pos then
local node_inv = node_meta:get_inventory()
for listname, size in pairs(data.meta.inventory) do
iinv:set_size(listname, size)
node_inv:set_size(listname, size)
for i, itemstack in ipairs(inv:get_list(listname)) do
-- add everything, putting leftovers back in the main inventory
inv:set_stack(listname, i, iinv:add_item(listname, itemstack))
inv:set_stack(listname, i, node_inv:add_item(listname, itemstack))
end
end
@ -407,15 +424,17 @@ digtron.deconstruct = function(digtron_id, root_pos, player_name)
-- Ensure node metadata fields are all set, too
for field, value in pairs(data.meta.fields) do
imeta:set_string(field, value)
node_meta:set_string(field, value)
end
-- Clear digtron_id, this node is no longer part of an active digtron
imeta:set_string("digtron_id", "")
node_meta:set_string("digtron_id", "")
end
end
dispose_id(digtron_id)
return root_pos
end
-- Removes the in-world nodes of a digtron
@ -435,9 +454,9 @@ digtron.remove_from_world = function(digtron_id, root_pos, player_name)
local root_hash = minetest.hash_node_position(root_pos)
local nodes_to_destroy = {}
for hash, data in pairs(layout) do
local ipos, node, imeta = get_valid_data(digtron_id, root_hash, hash, data, "digtron.destroy")
if ipos then
table.insert(nodes_to_destroy, ipos)
local node_pos, node, node_meta = get_valid_data(digtron_id, root_hash, hash, data, "digtron.destroy")
if node_pos then
table.insert(nodes_to_destroy, node_pos)
end
end
@ -452,12 +471,12 @@ digtron.build_to_world = function(digtron_id, root_pos, player_name)
local permitted = true
for hash, data in pairs(layout) do
local ipos = minetest.get_position_from_hash(hash + root_hash - origin_hash)
local node = minetest.get_node(ipos)
local node_pos = minetest.get_position_from_hash(hash + root_hash - origin_hash)
local node = minetest.get_node(node_pos)
local node_def = minetest.registered_nodes[node.name]
-- TODO: lots of testing needed here
if not (node_def and node_def.buildable_to) then
minetest.chat_send_all("not permitted due to " .. node.name .. " at " .. minetest.pos_to_string(ipos))
minetest.chat_send_all("not permitted due to " .. node.name .. " at " .. minetest.pos_to_string(node_pos))
permitted = false
break
end
@ -466,22 +485,23 @@ digtron.build_to_world = function(digtron_id, root_pos, player_name)
if permitted then
-- TODO: voxelmanip might be better here, less likely than with destroy though since metadata needs to be written
for hash, data in pairs(layout) do
local ipos = minetest.get_position_from_hash(hash + root_hash - origin_hash)
minetest.set_node(ipos, data.node)
local meta = minetest.get_meta(ipos)
meta:set_string("digtron_id", digtron_id)
local node_pos = minetest.get_position_from_hash(hash + root_hash - origin_hash)
minetest.set_node(node_pos, data.node)
local meta = minetest.get_meta(node_pos)
for field, value in pairs(data.meta.fields) do
meta:set_string(field, value)
end
-- Not needed - local inventories not used by active digtron, will be restored if deconstructed
meta:set_string("digtron_id", digtron_id)
meta:mark_as_private("digtron_id")
-- Not needed - local inventories not used by active digtron, will be restored if disassembled
-- local inv = meta:get_inventory()
-- for listname, size in pairs(data.meta.inventory) do
-- inv:set_size(listname, size)
-- end
end
local bbox = retrieve_bounding_box(digtron_id)
bbox.root = root_pos
persist_bounding_box(digtron_id, bbox)
persist_pos(digtron_id, root_pos)
end
return permitted
@ -503,12 +523,13 @@ digtron.can_dig = function(pos, digger)
local node = minetest.get_node(pos)
local bbox = retrieve_bounding_box(digtron_id)
local root_pos = retrieve_pos(digtron_id)
local layout = retrieve_layout(digtron_id)
if bbox == nil or bbox.root == nil or layout == nil then
if bbox == nil or root_pos == nil or layout == nil then
-- Somehow, this belongs to a digtron id that's missing information that should exist in persistence.
local missing = ""
if bbox == nil then missing = missing .. "bounding_box " end
if bbox ~= nil and bbox.root == nil then missing = missing .. "root_pos " end
if root_pos == nil then missing = missing .. "root_pos " end
if layout == nil then missing = missing .. "layout " end
minetest.log("error", "[Digtron] can_dig was called on a " .. node.name .. " at location "
@ -520,7 +541,7 @@ digtron.can_dig = function(pos, digger)
return true
end
local root_hash = minetest.hash_node_position(bbox.root)
local root_hash = minetest.hash_node_position(root_pos)
local here_hash = minetest.hash_node_position(pos)
local layout_hash = here_hash - root_hash + origin_hash
local layout_data = layout[layout_hash]
@ -550,13 +571,10 @@ digtron.on_blast = function(pos, intensity)
local meta = minetest.get_meta(pos)
local digtron_id = meta:get_string("digtron_id")
if digtron_id ~= "" then
local bbox = retrieve_bounding_box(digtron_id)
if bbox.root then
digtron.deconstruct(digtron_id, bbox.root, "an explosion")
else
if not digtron.disassemble(digtron_id, "an explosion") then
minetest.log("error", "[Digtron] a digtron node at " .. minetest.pos_to_string(pos)
.. " was hit by an explosion and had digtron_id " .. digtron_id
.. " but didn't have a root position recorded, so it could not be deconstructed.")
.. " but didn't have a root position recorded, so it could not be disassembled.")
return
end
end
@ -569,3 +587,37 @@ digtron.on_blast = function(pos, intensity)
minetest.remove_node(pos)
return drops
end
------------------------------------------------------------------------------------
-- Creative trash
-- This is wrapped in an after() call as a workaround for to https://github.com/minetest/minetest/issues/8827
if minetest.get_modpath("creative") then
minetest.after(1, function()
if minetest.get_inventory({type="detached", name="creative_trash"}) then
if minetest.remove_detached_inventory("creative_trash") then
-- Create the trash field
local trash = minetest.create_detached_inventory("creative_trash", {
-- Allow the stack to be placed and remove it in on_put()
-- This allows the creative inventory to restore the stack
allow_put = function(inv, listname, index, stack, player)
return stack:get_count()
end,
on_put = function(inv, listname, index, stack, player)
local stack = inv:get_stack(listname, index)
local stack_meta = stack:get_meta()
local digtron_id = stack_meta:get_string("digtron_id")
if digtron_id ~= "" then
minetest.log("action", player:get_player_name() .. " disposed of " .. digtron_id
.. " in the creative inventory's trash receptacle.")
dispose_id(digtron_id)
end
inv:set_list(listname, {})
end,
})
trash:set_size("main", 1)
end
end
end)
end