mirror of
https://github.com/minetest-mods/digtron.git
synced 2024-11-10 17:13:49 +01:00
1264 lines
47 KiB
Lua
1264 lines
47 KiB
Lua
local mod_meta = digtron.mod_meta
|
|
|
|
local cache = {}
|
|
|
|
-- internationalization boilerplate
|
|
local MP = minetest.get_modpath(minetest.get_current_modname())
|
|
local S, NS = dofile(MP.."/intllib.lua")
|
|
|
|
|
|
--minetest.debug(dump(mod_meta:to_table()))
|
|
|
|
-- Wipes mod_meta
|
|
--for field, value in pairs(mod_meta:to_table().fields) do
|
|
-- mod_meta:set_string(field, "")
|
|
--end
|
|
|
|
local modpath = minetest.get_modpath(minetest.get_current_modname())
|
|
|
|
local inventory_functions = dofile(modpath.."/inventories.lua")
|
|
|
|
local retrieve_inventory = inventory_functions.retrieve_inventory
|
|
local persist_inventory = inventory_functions.persist_inventory
|
|
local get_predictive_inventory = inventory_functions.get_predictive_inventory
|
|
local commit_predictive_inventory = inventory_functions.commit_predictive_inventory
|
|
local clear_predictive_inventory = inventory_functions.clear_predictive_inventory
|
|
|
|
local protection_check = function(pos, player_name)
|
|
if minetest.is_protected(pos, player_name) and
|
|
not minetest.check_player_privs(player_name, "protection_bypass") then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
--------------------------------------------------------------------------------------
|
|
|
|
local create_new_id = function()
|
|
local digtron_id = "digtron" .. tostring(math.random(1, 2^21))
|
|
-- 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
|
|
return digtron_id
|
|
end
|
|
|
|
-- Deletes a Digtron record. Note: just throws everything away, this is not digtron.disassemble.
|
|
local dispose_callbacks = {}
|
|
local dispose_id = function(digtron_id)
|
|
-- name doesn't bother caching
|
|
mod_meta:set_string(digtron_id..":name", "")
|
|
|
|
-- inventory handles itself specially too
|
|
mod_meta:set_string(digtron_id..":inv", "")
|
|
minetest.remove_detached_inventory(digtron_id)
|
|
|
|
-- clears the cache tables
|
|
for i, func in ipairs(dispose_callbacks) do
|
|
func(digtron_id)
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------------------
|
|
-- Name
|
|
|
|
-- Not bothering with a dynamic table store for names, they're just strings with no need for serialization or deserialization
|
|
local get_name = function(digtron_id)
|
|
local digtron_name = mod_meta:get_string(digtron_id..":name")
|
|
if digtron_name == "" then
|
|
return S("Unnamed Digtron")
|
|
else
|
|
return digtron_name
|
|
end
|
|
end
|
|
|
|
local set_name = function(digtron_id, 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
|
|
|
|
-------------------------------------------------------------------------------------------------------
|
|
-- Tables stored to metadata and cached locally
|
|
|
|
local get_table_functions = function(identifier)
|
|
cache[identifier] = {}
|
|
|
|
local persist_func = function(digtron_id, tbl)
|
|
mod_meta:set_string(digtron_id..":"..identifier, minetest.serialize(tbl))
|
|
cache[identifier][digtron_id] = tbl
|
|
end
|
|
|
|
local retrieve_func = function(digtron_id)
|
|
local current = cache[identifier][digtron_id]
|
|
if current then
|
|
return current
|
|
end
|
|
local tbl_string = mod_meta:get_string(digtron_id..":"..identifier)
|
|
if tbl_string ~= "" then
|
|
current = minetest.deserialize(tbl_string)
|
|
if current then
|
|
cache[identifier][digtron_id] = current
|
|
end
|
|
return current
|
|
end
|
|
return nil
|
|
end
|
|
|
|
local dispose_func = function(digtron_id)
|
|
mod_meta:set_string(digtron_id..":"..identifier, "")
|
|
cache[identifier][digtron_id] = nil
|
|
end
|
|
|
|
-- add a callback for dispose_id
|
|
table.insert(dispose_callbacks, dispose_func)
|
|
|
|
return persist_func, retrieve_func, dispose_func
|
|
end
|
|
|
|
local persist_layout, retrieve_layout = get_table_functions("layout")
|
|
local persist_pos, retrieve_pos, dispose_pos = get_table_functions("pos")
|
|
local persist_sequence, retrieve_sequence = get_table_functions("sequence")
|
|
local persist_step, retrieve_step = get_table_functions("step") -- actually just an integer, but table_functions works for that too
|
|
|
|
-------------------------------------------------------------------------------------------------------
|
|
-- Layout creation helpers
|
|
|
|
-- 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)
|
|
for _, dir in ipairs(digtron.cardinal_dirs) do
|
|
local test_pos = vector.add(pos, dir)
|
|
local test_hash = minetest.hash_node_position(test_pos)
|
|
if not (digtron_nodes[test_hash] or digtron_adjacent[test_hash]) then -- don't test twice
|
|
local test_node = minetest.get_node(test_pos)
|
|
local group_value = minetest.get_item_group(test_node.name, "digtron")
|
|
if group_value > 0 then
|
|
local meta = minetest.get_meta(test_pos)
|
|
if meta:contains("digtron_id") then
|
|
-- Node is part of an existing digtron, don't incorporate it
|
|
digtron_adjacent[test_hash] = true
|
|
--elseif TODO test for protected node status using player_name
|
|
else
|
|
--test_node.group_value = group_value -- for later ease of reference
|
|
digtron_nodes[test_hash] = test_node
|
|
get_all_digtron_nodes(test_pos, digtron_nodes, digtron_adjacent, player_name) -- recurse
|
|
end
|
|
else
|
|
-- don't record details, just keeping track of Digtron's borders
|
|
digtron_adjacent[test_hash] = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-------------------------------------------------------------------------------------------------
|
|
-- Cache-only data, not persisted
|
|
|
|
cache_bounding_box = {}
|
|
local update_bounding_box = function(bounding_box, pos)
|
|
bounding_box.minp.x = math.min(bounding_box.minp.x, pos.x)
|
|
bounding_box.minp.y = math.min(bounding_box.minp.y, pos.y)
|
|
bounding_box.minp.z = math.min(bounding_box.minp.z, pos.z)
|
|
bounding_box.maxp.x = math.max(bounding_box.maxp.x, pos.x)
|
|
bounding_box.maxp.y = math.max(bounding_box.maxp.y, pos.y)
|
|
bounding_box.maxp.z = math.max(bounding_box.maxp.z, pos.z)
|
|
end
|
|
local retrieve_bounding_box = function(digtron_id)
|
|
local val = cache_bounding_box[digtron_id]
|
|
if val then return val end
|
|
|
|
local layout = retrieve_layout(digtron_id)
|
|
if layout == nil then return nil end
|
|
|
|
local bbox = {minp = {x=0, y=0, z=0}, maxp = {x=0, y=0, z=0}}
|
|
for hash, data in pairs(layout) do
|
|
update_bounding_box(bbox, minetest.get_position_from_hash(hash))
|
|
end
|
|
cache_bounding_box[digtron_id] = bbox
|
|
return bbox
|
|
end
|
|
|
|
cache_all_adjacent_pos = {}
|
|
cache_all_digger_targets = {}
|
|
cache_all_builder_targets = {}
|
|
local refresh_adjacent = function(digtron_id)
|
|
local layout = retrieve_layout(digtron_id)
|
|
if layout == nil then return nil end
|
|
|
|
local adjacent = {} -- all adjacent nodes. TODO: if implementing traction wheels, won't be needed
|
|
local adjacent_to_diggers = {}
|
|
local adjacent_to_builders = {}
|
|
for hash, data in pairs(layout) do
|
|
for _, dir_hash in ipairs(digtron.cardinal_dirs_hash) do
|
|
local potential_adjacent = hash + dir_hash
|
|
if layout[potential_adjacent] == nil then
|
|
adjacent[potential_adjacent] = true
|
|
end
|
|
end
|
|
|
|
local digtron_group = minetest.get_item_group(data.node.name, "digtron")
|
|
|
|
if digtron_group == 3 or digtron_group == 10 then
|
|
local dir_hashes = {}
|
|
local dir_hash = digtron.facedir_to_dir_hash(data.node.param2)
|
|
local potential_target = hash + dir_hash -- pointed at this hash
|
|
if layout[potential_target] == nil then -- not pointed at another Digtron node
|
|
table.insert(dir_hashes, dir_hash)
|
|
end
|
|
|
|
if digtron_group == 10 then
|
|
dir_hash = digtron.facedir_to_down_hash(data.node.param2)
|
|
potential_target = hash + dir_hash -- pointed at this hash
|
|
if layout[potential_target] == nil then -- not pointed at another Digtron node
|
|
table.insert(dir_hashes, dir_hash)
|
|
end
|
|
end
|
|
|
|
if #dir_hashes > 0 then
|
|
local fields = data.meta.fields
|
|
adjacent_to_diggers[hash] = {
|
|
period = tonumber(fields.period) or 1,
|
|
offset = tonumber(fields.offset) or 0,
|
|
dir_hashes = dir_hashes,
|
|
}
|
|
end
|
|
end
|
|
if digtron_group == 4 then
|
|
local dir_hash = digtron.facedir_to_dir_hash(data.node.param2)
|
|
local potential_target = hash + dir_hash
|
|
if layout[potential_target] == nil then
|
|
local fields = data.meta.fields
|
|
-- TODO: trace extrusion and if it intersects Digtron layout cap it there.
|
|
adjacent_to_builders[hash] = {
|
|
period = tonumber(fields.period) or 1,
|
|
offset = tonumber(fields.offset) or 0,
|
|
item = fields.item,
|
|
facing = tonumber(fields.facing) or 0, -- facing of built node
|
|
extrusion = tonumber(fields.extrusion) or 1,
|
|
dir_hash = dir_hash, -- Record in table form, it'll be more convenient for use later
|
|
}
|
|
end
|
|
end
|
|
end
|
|
cache_all_adjacent_pos[digtron_id] = adjacent
|
|
cache_all_digger_targets[digtron_id] = adjacent_to_diggers
|
|
cache_all_builder_targets[digtron_id] = adjacent_to_builders
|
|
end
|
|
local retrieve_all_adjacent_pos = function(digtron_id)
|
|
local val = cache_all_adjacent_pos[digtron_id]
|
|
if val then return val end
|
|
refresh_adjacent(digtron_id)
|
|
return cache_all_adjacent_pos[digtron_id]
|
|
end
|
|
local retrieve_all_digger_targets = function(digtron_id)
|
|
local val = cache_all_digger_targets[digtron_id]
|
|
if val then return val end
|
|
refresh_adjacent(digtron_id)
|
|
return cache_all_digger_targets[digtron_id]
|
|
end
|
|
local retrieve_all_builder_targets = function(digtron_id)
|
|
local val = cache_all_builder_targets[digtron_id]
|
|
if val then return val end
|
|
refresh_adjacent(digtron_id)
|
|
return cache_all_builder_targets[digtron_id]
|
|
end
|
|
|
|
-- call this whenever a stored layout is modified (eg, by rotating it)
|
|
-- automatically called on dispose
|
|
local invalidate_layout_cache = function(digtron_id)
|
|
cache_bounding_box[digtron_id] = nil
|
|
cache_all_adjacent_pos[digtron_id] = nil
|
|
cache_all_digger_targets[digtron_id] = nil
|
|
cache_all_builder_targets[digtron_id] = nil
|
|
end
|
|
table.insert(dispose_callbacks, invalidate_layout_cache)
|
|
|
|
--------------------------------------------------------------------------------------------------------
|
|
-- assemble and disassemble
|
|
|
|
-- Returns the id of the new Digtron record, or nil on failure
|
|
local assemble = function(root_pos, player_name)
|
|
local root_node = minetest.get_node(root_pos)
|
|
|
|
local root_meta = minetest.get_meta(root_pos)
|
|
if root_meta:contains("digtron_id") then
|
|
-- 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 digtron_name = root_meta:get_string("infotext")
|
|
|
|
-- This should be called on an unassembled node.
|
|
if root_node.name ~= "digtron:controller_unassembled" then
|
|
-- Called on an incorrect node
|
|
minetest.log("error", "[Digtron] digtron.assemble called with pos " .. minetest.pos_to_string(root_pos)
|
|
.. " but the node at this location was " .. root_node.name)
|
|
return nil
|
|
end
|
|
|
|
local root_hash = minetest.hash_node_position(root_pos)
|
|
local digtron_nodes = {[root_hash] = root_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.
|
|
-- There's a slight inefficiency in throwing away digtron_adjacent when retrieve_all_adjacent_pos could
|
|
-- use this info, but it's small and IMO not worth the complexity.
|
|
get_all_digtron_nodes(root_pos, digtron_nodes, digtron_adjacent, player_name)
|
|
|
|
local digtron_id = create_new_id(root_pos)
|
|
local digtron_inv = retrieve_inventory(digtron_id)
|
|
|
|
local layout = {}
|
|
|
|
for hash, node in pairs(digtron_nodes) do
|
|
local relative_hash = minetest.hash_node_position(vector.subtract(minetest.get_position_from_hash(hash), root_pos))
|
|
|
|
local current_meta
|
|
if hash == root_hash then
|
|
current_meta = root_meta -- we're processing the controller, we already have a reference to its meta
|
|
else
|
|
current_meta = minetest.get_meta(minetest.get_position_from_hash(hash))
|
|
end
|
|
|
|
local current_meta_table = current_meta:to_table()
|
|
|
|
if current_meta_table.fields.digtron_id then
|
|
-- Trying to incorporate part of an existing digtron, should be impossible.
|
|
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)
|
|
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
|
|
digtron_inv:set_size(listname, digtron_inv:get_size(listname) + count)
|
|
for _, stack in ipairs(items) do
|
|
digtron_inv:add_item(listname, stack)
|
|
end
|
|
-- erase actual items from stored layout metadata, the detached inventory is authoritative
|
|
-- store the inventory size so the inventory can be easily recreated
|
|
current_meta_table.inventory[listname] = #items
|
|
end
|
|
|
|
local node_def = minetest.registered_nodes[node.name]
|
|
if node_def and node_def._digtron_assembled_node then
|
|
node.name = node_def._digtron_assembled_node
|
|
minetest.swap_node(minetest.get_position_from_hash(hash), node)
|
|
end
|
|
|
|
node.param1 = nil -- we don't care about param1, wipe it to save space
|
|
layout[relative_hash] = {meta = current_meta_table, node = node}
|
|
end
|
|
|
|
persist_inventory(digtron_id)
|
|
persist_layout(digtron_id, layout)
|
|
set_name(digtron_id, digtron_name)
|
|
invalidate_layout_cache(digtron_id)
|
|
persist_pos(digtron_id, root_pos)
|
|
persist_sequence(digtron_id, {{2,1}}) -- TODO find a better place to set a default like this
|
|
|
|
-- 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 node_meta
|
|
if hash == root_hash then
|
|
node_meta = root_meta -- we're processing the controller, we already have a reference to its meta
|
|
else
|
|
node_meta = minetest.get_meta(minetest.get_position_from_hash(hash))
|
|
end
|
|
local inv = node_meta:get_inventory()
|
|
|
|
for listname, items in pairs(inv:get_lists()) do
|
|
for i = 1, #items do
|
|
inv:set_stack(listname, i, ItemStack(""))
|
|
end
|
|
end
|
|
|
|
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.sound_play("digtron_machine_assemble", {gain = 0.5, pos=root_pos})
|
|
|
|
return digtron_id
|
|
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_pos, hash, data, function_name)
|
|
local node_pos = vector.add(minetest.get_position_from_hash(hash), root_pos)
|
|
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(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(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(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.")
|
|
node_meta:set_string("digtron_id", digtron_id)
|
|
node_meta:mark_as_private("digtron_id")
|
|
return node_pos, node, node_meta
|
|
end
|
|
end
|
|
return node_pos, node, node_meta
|
|
end
|
|
|
|
-- Turns the Digtron back into pieces
|
|
local disassemble = function(digtron_id, player_name)
|
|
local root_pos = retrieve_pos(digtron_id)
|
|
if not root_pos then
|
|
minetest.log("error", "[Digtron] digtron.disassemble was unable to find a position for " .. digtron_id
|
|
.. ", disassembly was impossible. Has the digtron been removed from world?")
|
|
return
|
|
end
|
|
|
|
local layout = retrieve_layout(digtron_id)
|
|
local inv = retrieve_inventory(digtron_id)
|
|
|
|
if not (layout and inv) then
|
|
minetest.log("error", "[Digtron] 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
|
|
|
|
-- Write metadata and inventory to in-world node at this location
|
|
for hash, data in pairs(layout) do
|
|
local node_pos, node, node_meta = get_valid_data(digtron_id, root_pos, hash, data, "disassemble")
|
|
|
|
if node_pos then
|
|
local node_inv = node_meta:get_inventory()
|
|
for listname, size in pairs(data.meta.inventory) do
|
|
node_inv:set_size(listname, size)
|
|
local digtron_inv_list = inv:get_list(listname)
|
|
if digtron_inv_list then
|
|
for i, itemstack in ipairs(digtron_inv_list) do
|
|
-- add everything, putting leftovers back in the main inventory
|
|
inv:set_stack(listname, i, node_inv:add_item(listname, itemstack))
|
|
end
|
|
else
|
|
minetest.log("warning", "[Digtron] inventory list " .. listname .. " existed in " .. node.name
|
|
.. " that was part of " .. digtron_id .. " but was not present in the detached inventory for this digtron."
|
|
.. " This should not have happened, please report an issue to Digtron programmers,"
|
|
.. " but it shouldn't impact digtron disassembly.")
|
|
end
|
|
end
|
|
|
|
local node_def = minetest.registered_nodes[node.name]
|
|
if node_def and node_def._digtron_disassembled_node then
|
|
minetest.swap_node(node_pos, {name=node_def._digtron_disassembled_node, param2=node.param2})
|
|
end
|
|
|
|
-- Ensure node metadata fields are all set, too
|
|
for field, value in pairs(data.meta.fields) do
|
|
node_meta:set_string(field, value)
|
|
end
|
|
|
|
-- Clear digtron_id, this node is no longer part of an active digtron
|
|
node_meta:set_string("digtron_id", "")
|
|
end
|
|
end
|
|
|
|
-- replace the controller node with the disassembled version
|
|
local root_node = minetest.get_node(root_pos)
|
|
if root_node.name == "digtron:controller" then
|
|
root_node.name = "digtron:controller_disassembled"
|
|
minetest.set_node(root_pos, root_node)
|
|
end
|
|
local root_meta = minetest.get_meta(root_pos)
|
|
root_meta:set_string("infotext", get_name(digtron_id))
|
|
|
|
minetest.log("action", "Digtron " .. digtron_id .. " disassembled at " .. minetest.pos_to_string(root_pos)
|
|
.. " by " .. player_name)
|
|
minetest.sound_play("digtron_machine_disassemble", {gain = 0.5, pos=root_pos})
|
|
|
|
dispose_id(digtron_id)
|
|
|
|
return root_pos
|
|
end
|
|
|
|
------------------------------------------------------------------------------------------
|
|
-- Moving Digtrons around
|
|
|
|
-- Removes the in-world nodes of a digtron
|
|
-- Does not destroy its layout info
|
|
-- returns a table of vectors of all the nodes that were removed, or nil on failure
|
|
local remove_from_world = function(digtron_id, player_name)
|
|
local layout = retrieve_layout(digtron_id)
|
|
local root_pos = retrieve_pos(digtron_id)
|
|
|
|
if not layout then
|
|
minetest.log("error", "[Digtron] digtron.remove_from_world Unable to find layout record for " .. digtron_id
|
|
.. ", wiping any remaining metadata for this id to prevent corruption. Sorry!")
|
|
if root_pos then
|
|
local meta = minetest.get_meta(root_pos)
|
|
meta:set_string("digtron_id", "")
|
|
end
|
|
dispose_id(digtron_id)
|
|
return nil
|
|
end
|
|
|
|
if not root_pos then
|
|
minetest.log("error", "[Digtron] digtron.remove_from_world Unable to find position for " .. digtron_id
|
|
.. ", it may have already been removed from the world.")
|
|
return nil
|
|
end
|
|
|
|
local nodes_to_destroy = {}
|
|
for hash, data in pairs(layout) do
|
|
local node_pos = get_valid_data(digtron_id, root_pos, hash, data, "remove_from_world")
|
|
if node_pos then
|
|
table.insert(nodes_to_destroy, node_pos)
|
|
end
|
|
end
|
|
|
|
minetest.bulk_set_node(nodes_to_destroy, {name="air"})
|
|
dispose_pos(digtron_id)
|
|
return nodes_to_destroy
|
|
end
|
|
|
|
-- Tests if a Digtron can be built at the designated location
|
|
local is_buildable_to = function(digtron_id, layout, root_pos, player_name, ignore_nodes, return_immediately_on_failure)
|
|
-- If this digtron is already in-world, we're likely testing as part of a movement attempt.
|
|
-- Record its existing node locations, they will be treated as buildable_to
|
|
local old_root_pos = retrieve_pos(digtron_id)
|
|
local old_layout = retrieve_layout(digtron_id)
|
|
if layout == nil then
|
|
layout = old_layout
|
|
end
|
|
|
|
local ignore_hashes = {}
|
|
if old_root_pos then
|
|
for hash, _ in pairs(old_layout) do
|
|
local old_hash = minetest.hash_node_position(vector.add(minetest.get_position_from_hash(hash), old_root_pos))
|
|
ignore_hashes[old_hash] = true
|
|
end
|
|
end
|
|
if ignore_nodes then
|
|
for _, ignore_pos in ipairs(ignore_nodes) do
|
|
ignore_hashes[minetest.hash_node_position(ignore_pos)] = true
|
|
end
|
|
end
|
|
|
|
local succeeded = {}
|
|
local failed = {}
|
|
local permitted = true
|
|
|
|
for hash, data in pairs(layout) do
|
|
-- Don't use get_valid_data, the Digtron isn't in-world yet
|
|
local node_pos = vector.add(minetest.get_position_from_hash(hash), root_pos)
|
|
local node_hash = minetest.hash_node_position(node_pos)
|
|
local node = minetest.get_node(node_pos)
|
|
local node_def = minetest.registered_nodes[node.name]
|
|
if not (
|
|
(node_def and node_def.buildable_to)
|
|
or ignore_hashes[node_hash]) or
|
|
protection_check(node_pos, player_name)
|
|
then
|
|
if return_immediately_on_failure then
|
|
return false -- no need to test further, don't return node positions
|
|
else
|
|
permitted = false
|
|
table.insert(failed, node_pos)
|
|
end
|
|
elseif not return_immediately_on_failure then
|
|
table.insert(succeeded, node_pos)
|
|
end
|
|
end
|
|
|
|
return permitted, succeeded, failed
|
|
end
|
|
|
|
-- Places the Digtron into the world.
|
|
local build_to_world = function(digtron_id, layout, root_pos, player_name)
|
|
if layout == nil then
|
|
layout = retrieve_layout(digtron_id)
|
|
end
|
|
local built_positions = {}
|
|
for hash, data in pairs(layout) do
|
|
-- Don't use get_valid_data, the Digtron isn't in-world yet
|
|
local node_pos = vector.add(minetest.get_position_from_hash(hash), root_pos)
|
|
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
|
|
meta:set_string("digtron_id", digtron_id)
|
|
meta:mark_as_private("digtron_id")
|
|
table.insert(built_positions, node_pos)
|
|
end
|
|
persist_pos(digtron_id, root_pos)
|
|
|
|
return built_positions
|
|
end
|
|
|
|
local move = function(digtron_id, dest_pos, player_name)
|
|
local layout = retrieve_layout(digtron_id)
|
|
local permitted, succeeded, failed = is_buildable_to(digtron_id, layout, dest_pos, player_name)
|
|
if permitted then
|
|
local removed = remove_from_world(digtron_id, player_name)
|
|
if removed then
|
|
build_to_world(digtron_id, layout, dest_pos, player_name)
|
|
minetest.sound_play("digtron_truck", {gain = 0.5, pos=dest_pos})
|
|
for _, removed_pos in ipairs(removed) do
|
|
minetest.check_for_falling(removed_pos)
|
|
end
|
|
end
|
|
else
|
|
digtron.show_buildable_nodes({}, failed)
|
|
minetest.sound_play("digtron_squeal", {gain = 0.5, pos=dest_pos})
|
|
end
|
|
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)
|
|
local rotated_layout = {}
|
|
for hash, data in pairs(layout) do
|
|
local duplicate_data = deep_copy(data)
|
|
-- Facings
|
|
local node_name = duplicate_data.node.name
|
|
local node_def = minetest.registered_nodes[node_name]
|
|
if node_def.paramtype2 == "wallmounted" then
|
|
duplicate_data.node.param2 = digtron.rotate_wallmounted(axis_hash, duplicate_data.node.param2)
|
|
elseif node_def.paramtype2 == "facedir" then
|
|
duplicate_data.node.param2 = digtron.rotate_facedir(axis_hash, duplicate_data.node.param2)
|
|
end
|
|
|
|
-- Rotate builder item facings
|
|
if minetest.get_item_group(node_name, "digtron") == 4 then
|
|
local build_item = duplicate_data.meta.fields.item
|
|
local build_item_def = minetest.registered_items[build_item]
|
|
if build_item_def.paramtype2 == "wallmounted" then
|
|
duplicate_data.meta.fields.facing = digtron.rotate_wallmounted(axis_hash, tonumber(duplicate_data.meta.fields.facing))
|
|
elseif build_item_def.paramtype2 == "facedir" then
|
|
duplicate_data.meta.fields.facing = digtron.rotate_facedir(axis_hash, tonumber(duplicate_data.meta.fields.facing))
|
|
end
|
|
end
|
|
|
|
-- Position
|
|
local pos = minetest.get_position_from_hash(hash)
|
|
pos = digtron.rotate_pos(axis_hash, pos)
|
|
local new_hash = minetest.hash_node_position(pos)
|
|
rotated_layout[new_hash] = duplicate_data
|
|
end
|
|
|
|
return rotated_layout
|
|
end
|
|
|
|
local rotate = function(digtron_id, axis, player_name)
|
|
local rotated_layout = rotate_layout(digtron_id, axis)
|
|
local root_pos = retrieve_pos(digtron_id)
|
|
local permitted, succeeded, failed = is_buildable_to(digtron_id, rotated_layout, root_pos, player_name)
|
|
if permitted then
|
|
local removed = remove_from_world(digtron_id, player_name)
|
|
if removed then
|
|
build_to_world(digtron_id, rotated_layout, root_pos, player_name)
|
|
minetest.sound_play("digtron_hydraulic", {gain = 0.5, pos=dest_pos})
|
|
persist_layout(digtron_id, rotated_layout)
|
|
-- Don't need to do fancy callback checking for digtron nodes since I made all those
|
|
-- nodes and I know they don't have anything that needs to be done for them.
|
|
-- Just check for falling nodes.
|
|
for _, removed_pos in ipairs(removed) do
|
|
minetest.check_for_falling(removed_pos)
|
|
end
|
|
end
|
|
else
|
|
digtron.show_buildable_nodes({}, failed)
|
|
minetest.sound_play("digtron_squeal", {gain = 0.5, pos=root_pos})
|
|
end
|
|
end
|
|
|
|
------------------------------------------------------------------------------------
|
|
-- Digging
|
|
|
|
local predict_dig = function(digtron_id, player_name, controlling_coordinate)
|
|
local predictive_inv = get_predictive_inventory(digtron_id)
|
|
local root_pos = retrieve_pos(digtron_id)
|
|
if not (root_pos and predictive_inv) then
|
|
minetest.log("error", "[Digtron] predict_dig failed to retrieve either "
|
|
.."a predictive inventory or a root position for " .. digtron_id)
|
|
return
|
|
end
|
|
|
|
local leftovers = {}
|
|
local dug_positions = {}
|
|
local cost = 0
|
|
local dug_hashes = {} -- to ensure the same node isn't dug twice
|
|
|
|
for digger_hash, digger_data in pairs(retrieve_all_digger_targets(digtron_id)) do
|
|
for _, dir_hash in ipairs(digger_data.dir_hashes) do
|
|
local target_hash = digger_hash + dir_hash
|
|
if not dug_hashes[target_hash] then
|
|
local target_pos = vector.add(minetest.get_position_from_hash(target_hash), root_pos)
|
|
local target_node = minetest.get_node(target_pos)
|
|
local target_name = target_node.name
|
|
local targetdef = minetest.registered_nodes[target_name]
|
|
if
|
|
(target_pos[controlling_coordinate] + digger_data.offset) % digger_data.period == 0 and -- test periodicity and offset
|
|
target_name ~= "air" and -- TODO: generalise this somehow for liquids and other undiggables
|
|
minetest.get_item_group(target_name, "digtron") == 0 and
|
|
minetest.get_item_group(target_name, "digtron_protected") == 0 and
|
|
minetest.get_item_group(target_name, "immortal") == 0 and
|
|
(
|
|
targetdef == nil or -- can dig undefined nodes, why not
|
|
targetdef.can_dig == nil or
|
|
targetdef.can_dig(target_pos, minetest.get_player_by_name(player_name))
|
|
) and
|
|
not protection_check(target_pos, player_name)
|
|
then
|
|
local material_cost = 0
|
|
if digtron.config.uses_resources then
|
|
local in_known_group = false
|
|
if minetest.get_item_group(target_name, "cracky") ~= 0 then
|
|
in_known_group = true
|
|
material_cost = math.max(material_cost, digtron.config.dig_cost_cracky)
|
|
end
|
|
if minetest.get_item_group(target_name, "crumbly") ~= 0 then
|
|
in_known_group = true
|
|
material_cost = math.max(material_cost, digtron.config.dig_cost_crumbly)
|
|
end
|
|
if minetest.get_item_group(target_name, "choppy") ~= 0 then
|
|
in_known_group = true
|
|
material_cost = math.max(material_cost, digtron.config.dig_cost_choppy)
|
|
end
|
|
if not in_known_group then
|
|
material_cost = digtron.config.dig_cost_default
|
|
end
|
|
end
|
|
cost = cost + material_cost
|
|
|
|
local drops = minetest.get_node_drops(target_name, "")
|
|
for _, drop in ipairs(drops) do
|
|
local leftover = predictive_inv:add_item("main", ItemStack(drop))
|
|
if leftover:get_count() > 0 then
|
|
table.insert(leftovers, leftover)
|
|
end
|
|
end
|
|
table.insert(dug_positions, target_pos)
|
|
dug_hashes[target_hash] = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return leftovers, dug_positions, cost
|
|
end
|
|
|
|
-- Removes nodes and records node info so execute_dug_callbacks can be called later
|
|
local get_and_remove_nodes = function(nodes_to_dig)
|
|
local ret = {}
|
|
for _, pos in ipairs(nodes_to_dig) do
|
|
local record = {}
|
|
record.pos = pos
|
|
record.node = minetest.get_node(pos)
|
|
record.meta = minetest.get_meta(pos)
|
|
minetest.remove_node(pos)
|
|
table.insert(ret, record)
|
|
end
|
|
return ret
|
|
end
|
|
|
|
local log_dug_nodes = function(nodes_to_dig, digtron_id, root_pos, player_name)
|
|
local nodes_dug_count = #nodes_to_dig
|
|
if nodes_dug_count > 0 then
|
|
local pluralized = "node"
|
|
if nodes_dug_count > 1 then
|
|
pluralized = "nodes"
|
|
end
|
|
minetest.log("action", nodes_dug_count .. " " .. pluralized .. " dug by "
|
|
.. digtron_id .. " near ".. minetest.pos_to_string(root_pos)
|
|
.. " operated by by " .. player_name)
|
|
end
|
|
end
|
|
|
|
-- Execute all the callbacks that would normally be called on a node after it's been dug.
|
|
-- This is a separate step from actually removing the nodes because we don't want to execute
|
|
-- these until after *everything* has been dug - this can trigger sand falling, we don't
|
|
-- want that getting in the way of nodes yet to be built.
|
|
local execute_dug_callbacks = function(nodes_dug)
|
|
-- Execute various on-dig callbacks for the nodes that Digtron dug
|
|
for _, dug_data in ipairs(nodes_dug) do
|
|
local old_pos = dug_data.pos
|
|
local old_node = dug_data.node
|
|
local old_name = old_node.name
|
|
|
|
for _, callback in ipairs(minetest.registered_on_dignodes) do
|
|
-- Copy pos and node because callback can modify them
|
|
local pos_copy = {x=old_pos.x, y=old_pos.y, z=old_pos.z}
|
|
local oldnode_copy = {name=old_name, param1=old_node.param1, param2=old_node.param2}
|
|
callback(pos_copy, oldnode_copy, digtron.fake_player)
|
|
end
|
|
|
|
local old_def = minetest.registered_nodes[old_name]
|
|
if old_def ~= nil then
|
|
local old_after_dig = old_def.after_dig_node
|
|
if old_after_dig ~= nil then
|
|
old_after_dig(old_pos, old_node, dug_data.meta, digtron.fake_player)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
------------------------------------------------------------------------------------------------------
|
|
-- Building
|
|
|
|
-- need to provide root_pos because Digtron moves before building
|
|
local predict_build = function(digtron_id, root_pos, player_name, ignore_nodes, controlling_coordinate)
|
|
local predictive_inv = get_predictive_inventory(digtron_id)
|
|
if not predictive_inv then
|
|
minetest.log("error", "[Digtron] predict_build failed to retrieve "
|
|
.."a predictive inventory for " .. digtron_id)
|
|
return
|
|
end
|
|
|
|
local ignore_hashes = {}
|
|
if ignore_nodes then
|
|
for _, ignore_pos in ipairs(ignore_nodes) do
|
|
ignore_hashes[minetest.hash_node_position(ignore_pos)] = true
|
|
end
|
|
end
|
|
|
|
local missing_items = {}
|
|
local built_nodes = {}
|
|
local cost = 0
|
|
|
|
for target_hash, builder_data in pairs(retrieve_all_builder_targets(digtron_id)) do
|
|
local dir_hash = builder_data.dir_hash
|
|
local periodicity_permitted = nil
|
|
for i = 1, builder_data.extrusion do
|
|
local target_pos = vector.add(minetest.get_position_from_hash(target_hash + i * dir_hash), root_pos)
|
|
if periodicity_permitted == nil then
|
|
-- test periodicity and offset once
|
|
periodicity_permitted = (target_pos[controlling_coordinate] + builder_data.offset) % builder_data.period == 0
|
|
if not periodicity_permitted then
|
|
break -- period/offset doesn't line up with the target
|
|
end
|
|
end
|
|
local target_node = minetest.get_node(target_pos)
|
|
local target_name = target_node.name
|
|
local targetdef = minetest.registered_nodes[target_name]
|
|
if
|
|
ignore_hashes[target_hash] or
|
|
(targetdef ~= nil
|
|
and targetdef.buildable_to
|
|
and not protection_check(target_pos, player_name)
|
|
)
|
|
then
|
|
local item = builder_data.item
|
|
local facing = builder_data.facing
|
|
|
|
local removed_item = predictive_inv:remove_item("main", ItemStack(item))
|
|
if removed_item:get_count() < 1 then
|
|
missing_items[item] = (missing_items[item] or 0) + 1
|
|
end
|
|
|
|
if digtron.config.uses_resources then
|
|
cost = cost + digtron.config.build_cost
|
|
end
|
|
|
|
table.insert(built_nodes, {
|
|
pos = target_pos,
|
|
node = {name=item, param2=facing },
|
|
old_node = target_node,
|
|
})
|
|
else
|
|
break -- extrusion reached an obstacle
|
|
end
|
|
end
|
|
end
|
|
|
|
return missing_items, built_nodes, cost
|
|
end
|
|
|
|
-- Place all items listed in built_nodes in-world, returning any itemstacks that item_place_node returned.
|
|
-- Also returns the number of successes for logging purposes
|
|
local build_nodes = function(built_nodes)
|
|
local leftovers = {}
|
|
local success_count = 0
|
|
for _, build_info in ipairs(built_nodes) do
|
|
local item_stack = ItemStack(build_info.node.name)
|
|
local buildpos = build_info.pos
|
|
local build_facing = build_info.node.param2
|
|
local returned_stack, success = digtron.item_place_node(item_stack, digtron.fake_player, buildpos, build_facing)
|
|
if success then
|
|
success_count = success_count + 1
|
|
end
|
|
if returned_stack:get_count() > 0 then
|
|
table.insert(leftovers, returned_stack)
|
|
end
|
|
end
|
|
return leftovers, success_count
|
|
end
|
|
|
|
local log_built_nodes = function(success_count, digtron_id, root_pos, player_name)
|
|
if success_count > 0 then
|
|
local pluralized = "node"
|
|
if success_count > 1 then
|
|
pluralized = "nodes"
|
|
end
|
|
minetest.log("action", success_count .. " " .. pluralized .. " built by "
|
|
.. digtron_id .. " near ".. minetest.pos_to_string(root_pos)
|
|
.. " operated by by " .. player_name)
|
|
end
|
|
end
|
|
|
|
-- Execute all the callbacks that would normally be called on a node after it's been built.
|
|
-- This is a separate step from actually placing the nodes because we don't want to execute
|
|
-- these until after *everything* has been built - this can trigger sand falling, we don't
|
|
-- want that getting in the way of nodes yet to be built.
|
|
local execute_built_callbacks = function(built_nodes)
|
|
for _, build_info in ipairs(built_nodes) do
|
|
local new_pos = build_info.pos
|
|
local new_node = build_info.node
|
|
local old_node = build_info.old_node
|
|
for _, callback in ipairs(minetest.registered_on_placenodes) do
|
|
-- Copy pos and node because callback can modify them
|
|
local pos_copy = {x=new_pos.x, y=new_pos.y, z=new_pos.z}
|
|
local oldnode_copy = {name=old_node.name, param1=old_node.param1, param2=old_node.param2}
|
|
local newnode_copy = {name=new_node.name, param1=new_node.param1, param2=new_node.param2}
|
|
callback(pos_copy, newnode_copy, digtron.fake_player, oldnode_copy)
|
|
end
|
|
|
|
local new_def = minetest.registered_nodes[new_node.name]
|
|
if new_def ~= nil and new_def.after_place_node ~= nil then
|
|
new_def.after_place_node(new_pos, digtron.fake_player)
|
|
end
|
|
end
|
|
end
|
|
|
|
-------------------------------------------------------------------------------------------------------
|
|
-- Execute cycle
|
|
|
|
-- Used to determine which coordinate is being checked for periodicity. eg, if the digtron is moving in the z direction, then periodicity is checked for every n nodes in the z axis.
|
|
local get_controlling_coordinate = function(facedir)
|
|
-- used for determining builder period and offset
|
|
local dir = digtron.facedir_to_dir_map[facedir]
|
|
if dir == 1 or dir == 3 then
|
|
return "z"
|
|
elseif dir == 2 or dir == 4 then
|
|
return "x"
|
|
else
|
|
return "y"
|
|
end
|
|
end
|
|
|
|
-- Attempts to insert the item list into the digtron inventory, and whatever doesn't fit
|
|
-- gets placed as an item at pos
|
|
local insert_or_eject = function(digtron_id, item_list, pos)
|
|
local predictive_inv = get_predictive_inventory(digtron_id)
|
|
if not predictive_inv then
|
|
minetest.log("error", "[Digtron] predict_build failed to retrieve "
|
|
.."a predictive inventory for " .. digtron_id)
|
|
return
|
|
end
|
|
for _, item in ipairs(item_list) do
|
|
local final_leftover = predictive_inv:add_item("main", item)
|
|
minetest.item_drop(final_leftover, digtron.fake_player, pos)
|
|
end
|
|
end
|
|
|
|
-- TODO: the dig_down parameter is a bit hacky, see if I can come up with a better way to arrange this code
|
|
local execute_dig_move_build_cycle = function(digtron_id, player_name, dig_down)
|
|
local old_root_pos = retrieve_pos(digtron_id)
|
|
local root_node = minetest.get_node(old_root_pos)
|
|
local root_facedir = root_node.param2
|
|
local controlling_coordinate = get_controlling_coordinate(root_facedir)
|
|
|
|
local dig_leftovers, nodes_to_dig, dig_cost = predict_dig(digtron_id, player_name, controlling_coordinate)
|
|
local new_root_pos
|
|
|
|
if dig_down then
|
|
new_root_pos = vector.add(old_root_pos, digtron.facedir_to_down(root_facedir))
|
|
else
|
|
new_root_pos = vector.add(old_root_pos, digtron.facedir_to_dir(root_facedir))
|
|
end
|
|
|
|
local layout = retrieve_layout(digtron_id)
|
|
local buildable_to, succeeded, failed = digtron.is_buildable_to(digtron_id, layout, new_root_pos, player_name, nodes_to_dig)
|
|
local missing_items, built_nodes, build_cost
|
|
|
|
if dig_down then
|
|
missing_items = {}
|
|
built_nodes = {}
|
|
build_cost = 0
|
|
else
|
|
missing_items, built_nodes, build_cost = predict_build(digtron_id, new_root_pos, player_name, nodes_to_dig, controlling_coordinate)
|
|
end
|
|
|
|
if not buildable_to then
|
|
clear_predictive_inventory(digtron_id)
|
|
digtron.show_buildable_nodes({}, failed)
|
|
minetest.sound_play("digtron_squeal", {gain = 0.5, pos=old_root_pos})
|
|
minetest.chat_send_player(player_name, S("@1 at @2 has encountered an obstacle.",
|
|
get_name(digtron_id), minetest.pos_to_string(old_root_pos)))
|
|
elseif next(missing_items) ~= nil then
|
|
clear_predictive_inventory(digtron_id)
|
|
local items = {}
|
|
for item, count in ipairs(missing_items) do
|
|
local item_def = minetest.registered_items[item]
|
|
if item_def == nil then -- Shouldn't be a problem, but don't crash if it does happen somehow
|
|
table.insert(items, count .. " " .. item)
|
|
else
|
|
table.insert(items, count .. " " .. item_def.description)
|
|
end
|
|
end
|
|
minetest.chat_send_player(player_name, S("@1 at @2 requires @3 to execute its next build cycle.",
|
|
get_name(digtron_id), minetest.pos_to_string(old_root_pos), table.concat(items, ", ")))
|
|
minetest.sound_play("digtron_dingding", {gain = 0.5, pos=old_root_pos})
|
|
else
|
|
digtron.fake_player:update(old_root_pos, player_name)
|
|
|
|
-- Removing old nodes
|
|
local removed = digtron.remove_from_world(digtron_id, player_name)
|
|
if removed then
|
|
local nodes_dug = get_and_remove_nodes(nodes_to_dig, player_name)
|
|
log_dug_nodes(nodes_to_dig, digtron_id, old_root_pos, player_name)
|
|
|
|
-- Building new Digtron
|
|
digtron.build_to_world(digtron_id, layout, new_root_pos, player_name)
|
|
minetest.sound_play("digtron_construction", {gain = 0.5, pos=new_root_pos})
|
|
|
|
local build_leftovers, success_count = build_nodes(built_nodes, player_name)
|
|
log_built_nodes(success_count, digtron_id, old_root_pos, player_name)
|
|
|
|
-- Don't need to do fancy callback checking for digtron nodes since I made all those
|
|
-- nodes and I know they don't have anything that needs to be done for them.
|
|
-- Just check for falling nodes.
|
|
for _, removed_pos in ipairs(removed) do
|
|
minetest.check_for_falling(removed_pos)
|
|
end
|
|
|
|
-- Must be called after digtron.build_to_world because it triggers falling nodes
|
|
execute_dug_callbacks(nodes_dug)
|
|
execute_built_callbacks(built_nodes)
|
|
|
|
-- try putting dig_leftovers and build_leftovers into the inventory one last time before ejecting it
|
|
insert_or_eject(digtron_id, dig_leftovers, old_root_pos)
|
|
insert_or_eject(digtron_id, build_leftovers, old_root_pos)
|
|
|
|
commit_predictive_inventory(digtron_id)
|
|
end
|
|
end
|
|
end
|
|
|
|
---------------------------------------------------------------------------------
|
|
-- Node callbacks
|
|
|
|
-- If the digtron node has an assigned ID and a layout for that ID exists and
|
|
-- a matching node exists in the layout then don't let it be dug.
|
|
local can_dig = function(pos, digger)
|
|
if digger then
|
|
local player_name = digger:get_player_name()
|
|
if protection_check(pos, player_name) then
|
|
return false
|
|
end
|
|
end
|
|
|
|
local meta = minetest.get_meta(pos)
|
|
local digtron_id = meta:get_string("digtron_id")
|
|
if digtron_id == "" then
|
|
return true
|
|
end
|
|
|
|
local node = minetest.get_node(pos)
|
|
|
|
local root_pos = retrieve_pos(digtron_id)
|
|
local layout = retrieve_layout(digtron_id)
|
|
if 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 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 "
|
|
.. minetest.pos_to_string(pos) .. " that claimed to belong to " .. digtron_id
|
|
.. ". However, layout and/or location data are missing: " .. missing)
|
|
-- TODO May be better to do this to prevent node duplication. But we're already in bug land here so tread gently.
|
|
--minetest.remove_node(pos)
|
|
--return false
|
|
return true
|
|
end
|
|
|
|
local layout_hash = minetest.hash_node_position(vector.subtract(pos, root_pos))
|
|
local layout_data = layout[layout_hash]
|
|
if layout_data == nil or layout_data.node == nil then
|
|
minetest.log("error", "[Digtron] can_dig was called on a " .. node.name .. " at location "
|
|
.. minetest.pos_to_string(pos) .. " that claimed to belong to " .. digtron_id
|
|
.. ". However, the layout for that digtron_id didn't contain any corresponding node at its location.")
|
|
return true
|
|
end
|
|
if layout_data.node.name ~= node.name or layout_data.node.param2 ~= node.param2 then
|
|
minetest.log("error", "[Digtron] can_dig was called on a " .. node.name .. " with param2 "
|
|
.. node.param2 .." at location " .. minetest.pos_to_string(pos) .. " that belonged to " .. digtron_id
|
|
.. ". However, the layout for that digtron_id contained a " .. layout_data.node.name
|
|
.. "with param2 ".. layout_data.node.param2 .. " at its location.")
|
|
return true
|
|
end
|
|
|
|
-- We're part of a valid Digtron. No touchy.
|
|
return false
|
|
end
|
|
|
|
-- put this on all Digtron nodes. If other inventory types are added (eg, batteries)
|
|
-- update this.
|
|
local on_blast = function(pos, intensity)
|
|
if intensity < 1.0 then return end -- The Almighty Digtron ignores weak-ass explosions
|
|
|
|
local meta = minetest.get_meta(pos)
|
|
local digtron_id = meta:get_string("digtron_id")
|
|
if digtron_id ~= "" then
|
|
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 disassembled.")
|
|
return
|
|
end
|
|
end
|
|
|
|
local drops = {}
|
|
default.get_inventory_drops(pos, "main", drops)
|
|
default.get_inventory_drops(pos, "fuel", drops)
|
|
local node = minetest.get_node(pos)
|
|
table.insert(drops, ItemStack(node.name))
|
|
minetest.remove_node(pos)
|
|
return drops
|
|
end
|
|
|
|
-- Use this inside other on_rightclicks for configuring Digtron nodes, this
|
|
-- overrides if you're right-clicking with another Digtron node and assumes
|
|
-- that you're trying to build it.
|
|
local on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
|
|
local item_def = itemstack:get_definition()
|
|
if item_def.type == "node" and minetest.get_item_group(itemstack:get_name(), "digtron") > 0 then
|
|
local returnstack, success = minetest.item_place_node(itemstack, clicker, pointed_thing)
|
|
if success and item_def.sounds and item_def.sounds.place and item_def.sounds.place.name then
|
|
minetest.sound_play(item_def.sounds.place, {pos = pos})
|
|
end
|
|
return returnstack, success
|
|
end
|
|
end
|
|
|
|
------------------------------------------------------------------------------------
|
|
-- Creative trash
|
|
|
|
-- Catch when someone throws a Digtron controller with an ID into the trash, dispose
|
|
-- of the persisted layout.
|
|
if minetest.get_modpath("creative") then
|
|
local trash = minetest.detached_inventories["creative_trash"]
|
|
if trash then
|
|
local old_on_put = trash.on_put
|
|
if old_on_put then
|
|
local digtron_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 stack:get_name() == "digtron:controller" and 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
|
|
return old_on_put(inv, listname, index, stack, player)
|
|
end
|
|
trash.on_put = digtron_on_put
|
|
end
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------------
|
|
-- Fallback method for recovering missing metadata
|
|
-- If this gets called frequently then something's wrong.
|
|
|
|
local recover_digtron_id = function(root_pos)
|
|
for field, value in pairs(mod_meta:to_table().fields) do
|
|
local fields = field:split(":")
|
|
if #fields == 2 and fields[2] == "pos" and vector.equals(root_pos, minetest.deserialize(value)) then
|
|
local digtron_id = fields[1]
|
|
minetest.log("warning", "[Digtron] had to use recover_digtron_id to restore "
|
|
..digtron_id .. " to the controller at " .. minetest.pos_to_string(root_pos)
|
|
..". If this happens frequently please file an issue with Digtron's developers. "
|
|
.."recover_digtron_id will now attempt to restore the digtron_id metadata key to all "
|
|
.."nodes in this Digtron's layout.")
|
|
local layout = retrieve_layout(digtron_id)
|
|
for hash, data in pairs(layout) do
|
|
-- get_valid_data will attempt to repair node metadata that's missing digtron_id
|
|
local node_pos, node, node_meta = get_valid_data(digtron_id, root_pos, hash, data, "recover_digtron_id")
|
|
end
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------------------------------
|
|
-- External API
|
|
|
|
-- node definition methods
|
|
digtron.can_dig = can_dig
|
|
digtron.on_blast = on_blast
|
|
digtron.on_rightclick = on_rightclick
|
|
|
|
digtron.get_name = get_name
|
|
digtron.set_name = set_name
|
|
digtron.get_pos = retrieve_pos
|
|
digtron.get_bounding_box = retrieve_bounding_box
|
|
|
|
-- used by formspecs
|
|
digtron.get_inventory = retrieve_inventory
|
|
digtron.set_sequence = persist_sequence
|
|
digtron.get_sequence = retrieve_sequence
|
|
digtron.set_step = persist_step
|
|
digtron.get_step = retrieve_step
|
|
|
|
|
|
digtron.assemble = assemble
|
|
digtron.disassemble = disassemble
|
|
digtron.remove_from_world = remove_from_world
|
|
digtron.is_buildable_to = is_buildable_to
|
|
digtron.build_to_world = build_to_world
|
|
digtron.move = move
|
|
digtron.rotate = rotate
|
|
digtron.execute_dig_move_build_cycle = execute_dig_move_build_cycle
|
|
|
|
digtron.recover_digtron_id = recover_digtron_id |