2019-08-25 20:33:44 +02:00
local mod_meta = digtron.mod_meta
2019-08-18 07:12:12 +02:00
2019-08-20 08:54:18 +02:00
local cache = { }
2019-08-18 21:02:42 +02:00
2019-08-20 04:02:16 +02:00
--minetest.debug(dump(mod_meta:to_table()))
2019-08-19 07:11:24 +02:00
-- Wipes mod_meta
--for field, value in pairs(mod_meta:to_table().fields) do
-- mod_meta:set_string(field, "")
--end
2019-08-25 20:33:44 +02:00
local modpath = minetest.get_modpath ( minetest.get_current_modname ( ) )
2019-08-18 10:23:08 +02:00
2019-08-25 20:33:44 +02:00
local inventory_functions = dofile ( modpath .. " /inventories.lua " )
2019-08-18 10:23:08 +02:00
2019-08-25 20:33:44 +02:00
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
2019-08-18 07:12:12 +02:00
2019-08-25 23:52:35 +02:00
digtron.retrieve_inventory = retrieve_inventory -- used by formspecs
2019-08-18 10:23:08 +02:00
--------------------------------------------------------------------------------------
local create_new_id = function ( )
2019-08-20 08:29:26 +02:00
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
2019-08-26 01:16:23 +02:00
return digtron_id
2019-08-18 07:12:12 +02:00
end
2019-08-20 08:29:26 +02:00
-- Deletes a Digtron record. Note: just throws everything away, this is not digtron.disassemble.
2019-08-20 08:54:18 +02:00
local dispose_callbacks = { }
2019-08-18 23:29:15 +02:00
local dispose_id = function ( digtron_id )
2019-08-20 08:54:18 +02:00
-- name doesn't bother caching
2019-08-19 07:11:24 +02:00
mod_meta : set_string ( digtron_id .. " :name " , " " )
2019-08-20 08:54:18 +02:00
-- 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
2019-08-18 23:29:15 +02:00
end
--------------------------------------------------------------------------------------------
-- Name
2019-08-20 08:29:26 +02:00
-- Not bothering with a dynamic table store for names, they're just strings with no need for serialization or deserialization
2019-08-18 23:29:15 +02:00
digtron.get_name = function ( digtron_id )
2019-08-19 07:11:24 +02:00
return mod_meta : get_string ( digtron_id .. " :name " )
2019-08-18 23:29:15 +02:00
end
digtron.set_name = function ( digtron_id , digtron_name )
2019-08-20 08:29:26 +02:00
-- 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
2019-08-18 10:23:08 +02:00
end
-------------------------------------------------------------------------------------------------------
2019-08-20 08:54:18 +02:00
-- Tables stored to metadata and cached locally
local get_table_functions = function ( identifier )
cache [ identifier ] = { }
2019-08-23 05:16:08 +02:00
local persist_func = function ( digtron_id , tbl )
2019-08-19 07:11:24 +02:00
mod_meta : set_string ( digtron_id .. " : " .. identifier , minetest.serialize ( tbl ) )
2019-08-20 08:54:18 +02:00
cache [ identifier ] [ digtron_id ] = tbl
2019-08-23 05:16:08 +02:00
end
local retrieve_func = function ( digtron_id )
2019-08-20 08:54:18 +02:00
local current = cache [ identifier ] [ digtron_id ]
2019-08-18 21:02:42 +02:00
if current then
return current
end
2019-08-19 07:11:24 +02:00
local tbl_string = mod_meta : get_string ( digtron_id .. " : " .. identifier )
2019-08-18 21:02:42 +02:00
if tbl_string ~= " " then
current = minetest.deserialize ( tbl_string )
if current then
2019-08-20 08:54:18 +02:00
cache [ identifier ] [ digtron_id ] = current
2019-08-18 21:02:42 +02:00
end
return current
end
2019-08-18 10:23:08 +02:00
end
2019-08-23 05:16:08 +02:00
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
2019-08-18 07:12:12 +02:00
end
2019-08-20 08:54:18 +02:00
local persist_layout , retrieve_layout = get_table_functions ( " layout " )
2019-08-23 05:16:08 +02:00
local persist_pos , retrieve_pos , dispose_pos = get_table_functions ( " pos " )
2019-08-20 08:29:26 +02:00
digtron.get_pos = retrieve_pos
2019-08-18 07:12:12 +02:00
2019-08-20 08:54:18 +02:00
-------------------------------------------------------------------------------------------------------
-- Layout creation helpers
2019-08-25 06:22:40 +02:00
local cardinal_dirs = {
{ x = 0 , y = 0 , z = 1 } ,
{ x = 1 , y = 0 , z = 0 } ,
{ x = 0 , y = 0 , z =- 1 } ,
{ x =- 1 , y = 0 , z = 0 } ,
{ x = 0 , y =- 1 , z = 0 } ,
{ x = 0 , y = 1 , z = 0 } ,
}
-- Mapping from facedir value to index in cardinal_dirs.
local facedir_to_dir_map = {
[ 0 ] = 1 , 2 , 3 , 4 ,
5 , 2 , 6 , 4 ,
6 , 2 , 5 , 4 ,
1 , 5 , 3 , 6 ,
1 , 6 , 3 , 5 ,
1 , 4 , 3 , 2 ,
2019-08-18 07:12:12 +02:00
}
2019-08-25 06:22:40 +02:00
-- Turn the cardinal directions into a set of integers you can add to a hash to step in that direction.
local cardinal_dirs_hash = { }
for i , dir in ipairs ( cardinal_dirs ) do
cardinal_dirs_hash [ i ] = minetest.hash_node_position ( dir ) - minetest.hash_node_position ( { x = 0 , y = 0 , z = 0 } )
end
2019-08-25 23:52:35 +02:00
-- Given a facedir, returns an index into either cardinal_dirs or cardinal_dirs_hash that
-- can be used to determine what this node is "aimed" at
2019-08-25 06:22:40 +02:00
local facedir_to_dir_index = function ( param2 )
return facedir_to_dir_map [ param2 % 32 ]
2019-08-19 06:41:01 +02:00
end
2019-08-18 07:12:12 +02:00
-- recursive function searches out all connected unassigned digtron nodes
2019-08-25 06:22:40 +02:00
local get_all_digtron_nodes
get_all_digtron_nodes = function ( pos , digtron_nodes , digtron_adjacent , player_name )
for _ , dir in ipairs ( cardinal_dirs ) do
2019-08-18 07:12:12 +02:00
local test_pos = vector.add ( pos , dir )
local test_hash = minetest.hash_node_position ( test_pos )
2019-08-18 21:02:42 +02:00
if not ( digtron_nodes [ test_hash ] or digtron_adjacent [ test_hash ] ) then -- don't test twice
2019-08-18 07:12:12 +02:00
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
2019-08-18 21:02:42 +02:00
digtron_adjacent [ test_hash ] = true
2019-08-18 07:12:12 +02:00
--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
2019-08-25 06:22:40 +02:00
get_all_digtron_nodes ( test_pos , digtron_nodes , digtron_adjacent , player_name ) -- recurse
2019-08-18 07:12:12 +02:00
end
else
2019-08-25 23:52:35 +02:00
-- don't record details, just keeping track of Digtron's borders
2019-08-18 21:02:42 +02:00
digtron_adjacent [ test_hash ] = true
2019-08-18 07:12:12 +02:00
end
end
end
end
2019-08-25 06:22:40 +02:00
-------------------------------------------------------------------------------------------------
-- 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 = { }
local refresh_adjacent = function ( digtron_id )
local layout = retrieve_layout ( digtron_id )
if layout == nil then return nil end
local adjacent = { }
local adjacent_to_diggers = { }
for hash , data in pairs ( layout ) do
for _ , dir_hash in ipairs ( cardinal_dirs_hash ) do
local potential_adjacent = hash + dir_hash
if layout [ potential_adjacent ] == nil then
adjacent [ potential_adjacent ] = true
end
end
if minetest.get_item_group ( data.node . name , " digtron " ) == 3 then
local potential_target = hash + cardinal_dirs_hash [ facedir_to_dir_index ( data.node . param2 ) ]
if layout [ potential_target ] == nil then
local fields = data.meta . fields
2019-08-25 06:52:02 +02:00
adjacent_to_diggers [ potential_target ] = { period = fields.period , offset = fields.offset }
2019-08-25 06:22:40 +02:00
end
end
end
cache_all_adjacent_pos [ digtron_id ] = adjacent
cache_all_digger_targets [ digtron_id ] = adjacent_to_diggers
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
-- 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
end
table.insert ( dispose_callbacks , invalidate_layout_cache )
2019-08-18 21:02:42 +02:00
--------------------------------------------------------------------------------------------------------
2019-08-20 08:29:26 +02:00
-- assemble and disassemble
2019-08-18 21:02:42 +02:00
2019-08-18 07:12:12 +02:00
-- Returns the id of the new Digtron record, or nil on failure
2019-08-20 08:29:26 +02:00
digtron.assemble = function ( root_pos , player_name )
2019-08-18 23:29:15 +02:00
local node = minetest.get_node ( root_pos )
2019-08-20 04:02:16 +02:00
-- 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.
2019-08-18 07:12:12 +02:00
if node.name ~= " digtron:controller " then
-- Called on an incorrect node
2019-08-20 08:29:26 +02:00
minetest.log ( " error " , " [Digtron] digtron.assemble called with pos " .. minetest.pos_to_string ( root_pos )
2019-08-18 07:12:12 +02:00
.. " but the node at this location was " .. node.name )
return nil
end
2019-08-18 23:29:15 +02:00
local root_meta = minetest.get_meta ( root_pos )
if root_meta : contains ( " digtron_id " ) then
2019-08-20 08:29:26 +02:00
-- 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. " )
2019-08-18 07:12:12 +02:00
return nil
end
2019-08-18 23:29:15 +02:00
local root_hash = minetest.hash_node_position ( root_pos )
2019-08-20 04:02:16 +02:00
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
2019-08-25 23:52:35 +02:00
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.
2019-08-25 06:22:40 +02:00
get_all_digtron_nodes ( root_pos , digtron_nodes , digtron_adjacent , player_name )
2019-08-18 07:12:12 +02:00
2019-08-26 01:16:23 +02:00
local digtron_id = create_new_id ( root_pos )
local digtron_inv = retrieve_inventory ( digtron_id )
2019-08-18 07:12:12 +02:00
local layout = { }
for hash , node in pairs ( digtron_nodes ) do
2019-08-24 08:04:19 +02:00
local relative_hash = hash - root_hash
2019-08-18 23:29:15 +02:00
local current_meta
2019-08-18 07:12:12 +02:00
if hash == root_hash then
2019-08-18 23:29:15 +02:00
current_meta = root_meta -- we're processing the controller, we already have a reference to its meta
2019-08-18 07:12:12 +02:00
else
2019-08-18 23:29:15 +02:00
current_meta = minetest.get_meta ( minetest.get_position_from_hash ( hash ) )
2019-08-18 07:12:12 +02:00
end
2019-08-18 23:29:15 +02:00
local current_meta_table = current_meta : to_table ( )
2019-08-18 07:12:12 +02:00
2019-08-18 23:29:15 +02:00
if current_meta_table.fields . digtron_id then
2019-08-18 07:12:12 +02:00
-- Trying to incorporate part of an existing digtron, should be impossible.
2019-08-20 08:29:26 +02:00
minetest.log ( " error " , " [Digtron] digtron.assemble tried to incorporate a Digtron node of type "
2019-08-18 07:12:12 +02:00
.. node.name .. " at " .. minetest.pos_to_string ( minetest.get_position_from_hash ( hash ) )
2019-08-18 23:29:15 +02:00
.. " that was already assigned to digtron id " .. current_meta_table.fields . digtron_id )
2019-08-18 07:12:12 +02:00
dispose_id ( digtron_id )
return nil
end
-- Process inventories specially
-- TODO Builder inventory gets turned into an itemname in a special key in the builder's meta
-- fuel and main get added to corresponding detached inventory lists
2019-08-18 23:29:15 +02:00
for listname , items in pairs ( current_meta_table.inventory ) do
2019-08-18 07:12:12 +02:00
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
2019-08-18 21:02:42 +02:00
-- erase actual items from stored layout metadata, the detached inventory is authoritative
-- store the inventory size so the inventory can be easily recreated
2019-08-18 23:29:15 +02:00
current_meta_table.inventory [ listname ] = # items
2019-08-18 07:12:12 +02:00
end
2019-08-23 05:16:08 +02:00
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
2019-08-18 21:02:42 +02:00
2019-08-18 07:12:12 +02:00
node.param1 = nil -- we don't care about param1, wipe it to save space
2019-08-18 23:29:15 +02:00
layout [ relative_hash ] = { meta = current_meta_table , node = node }
2019-08-18 07:12:12 +02:00
end
2019-08-18 23:51:25 +02:00
digtron.set_name ( digtron_id , root_meta : get_string ( " infotext " ) )
2019-08-18 10:23:08 +02:00
persist_inventory ( digtron_id )
persist_layout ( digtron_id , layout )
2019-08-25 06:22:40 +02:00
invalidate_layout_cache ( digtron_id )
2019-08-20 08:29:26 +02:00
persist_pos ( digtron_id , root_pos )
2019-08-18 10:23:08 +02:00
-- 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
2019-08-20 08:29:26 +02:00
local node_meta
2019-08-18 10:23:08 +02:00
if hash == root_hash then
2019-08-20 08:29:26 +02:00
node_meta = root_meta -- we're processing the controller, we already have a reference to its meta
2019-08-18 10:23:08 +02:00
else
2019-08-20 08:29:26 +02:00
node_meta = minetest.get_meta ( minetest.get_position_from_hash ( hash ) )
2019-08-18 10:23:08 +02:00
end
2019-08-20 08:29:26 +02:00
local inv = node_meta : get_inventory ( )
2019-08-18 10:23:08 +02:00
2019-08-18 21:02:42 +02:00
for listname , items in pairs ( inv : get_lists ( ) ) do
for i = 1 , # items do
inv : set_stack ( listname , i , ItemStack ( " " ) )
end
end
2019-08-20 08:29:26 +02:00
node_meta : set_string ( " digtron_id " , digtron_id )
node_meta : mark_as_private ( " digtron_id " )
2019-08-18 10:23:08 +02:00
end
2019-08-20 08:29:26 +02:00
minetest.log ( " action " , " Digtron " .. digtron_id .. " assembled at " .. minetest.pos_to_string ( root_pos )
.. " by " .. player_name )
2019-08-23 05:16:08 +02:00
minetest.sound_play ( " digtron_machine_assemble " , { gain = 0.5 , pos = root_pos } )
2019-08-18 10:23:08 +02:00
2019-08-18 07:12:12 +02:00
return digtron_id
end
2019-08-19 02:23:51 +02:00
-- 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 )
2019-08-24 08:04:19 +02:00
local node_hash = hash + root_hash -- TODO may want to return this as well?
2019-08-23 05:16:08 +02:00
local node_pos = minetest.get_position_from_hash ( node_hash )
2019-08-20 08:29:26 +02:00
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 " )
2019-08-19 02:23:51 +02:00
if data.node . name ~= node.name then
minetest.log ( " error " , " [Digtron] " .. function_name .. " tried interacting with one of " .. digtron_id .. " 's "
2019-08-20 08:29:26 +02:00
.. data.node . name .. " s at " .. minetest.pos_to_string ( node_pos ) .. " but the node at that location was of type "
2019-08-19 02:23:51 +02:00
.. node.name )
2019-08-20 04:02:16 +02:00
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 "
2019-08-20 08:29:26 +02:00
.. data.node . name .. " at " .. minetest.pos_to_string ( node_pos )
2019-08-20 04:02:16 +02:00
.. " 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 "
2019-08-20 08:29:26 +02:00
.. data.node . name .. " at " .. minetest.pos_to_string ( node_pos )
2019-08-20 04:02:16 +02:00
.. " 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. " )
2019-08-20 08:29:26 +02:00
return node_pos , node , node_meta
2019-08-20 04:02:16 +02:00
end
2019-08-19 02:23:51 +02:00
end
2019-08-20 08:29:26 +02:00
return node_pos , node , node_meta
2019-08-19 02:23:51 +02:00
end
-- Turns the Digtron back into pieces
2019-08-20 08:29:26 +02:00
digtron.disassemble = function ( digtron_id , player_name )
local bbox = retrieve_bounding_box ( digtron_id )
local root_pos = retrieve_pos ( digtron_id )
2019-08-24 08:04:19 +02:00
if not root_pos then
2019-08-26 04:09:09 +02:00
minetest.log ( " error " , " [Digtron] digtron.disassemble was unable to find a position for " .. digtron_id
2019-08-24 08:04:19 +02:00
.. " , disassembly was impossible. Has the digtron been removed from world? " )
return
end
2019-08-20 08:29:26 +02:00
2019-08-18 23:29:15 +02:00
local root_meta = minetest.get_meta ( root_pos )
2019-08-18 23:51:25 +02:00
root_meta : set_string ( " infotext " , digtron.get_name ( digtron_id ) )
2019-08-18 23:29:15 +02:00
2019-08-18 21:02:42 +02:00
local layout = retrieve_layout ( digtron_id )
local inv = digtron.retrieve_inventory ( digtron_id )
2019-08-19 07:11:24 +02:00
if not ( layout and inv ) then
2019-08-26 04:09:09 +02:00
minetest.log ( " error " , " [Digtron] digtron.disassemble was unable to find either layout or inventory record for " .. digtron_id
2019-08-20 08:29:26 +02:00
.. " , disassembly was impossible. Clearing any other remaining data for this id. " )
2019-08-19 07:11:24 +02:00
dispose_id ( digtron_id )
return
end
2019-08-18 23:29:15 +02:00
local root_hash = minetest.hash_node_position ( root_pos )
2019-08-18 10:23:08 +02:00
2019-08-18 21:02:42 +02:00
-- Write metadata and inventory to in-world node at this location
for hash , data in pairs ( layout ) do
2019-08-20 08:29:26 +02:00
local node_pos , node , node_meta = get_valid_data ( digtron_id , root_hash , hash , data , " digtron.disassemble " )
2019-08-19 02:23:51 +02:00
2019-08-20 08:29:26 +02:00
if node_pos then
local node_inv = node_meta : get_inventory ( )
2019-08-18 21:02:42 +02:00
for listname , size in pairs ( data.meta . inventory ) do
2019-08-20 08:29:26 +02:00
node_inv : set_size ( listname , size )
2019-08-25 20:33:44 +02:00
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. " )
2019-08-18 21:02:42 +02:00
end
end
2019-08-23 05:16:08 +02:00
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
2019-08-18 21:02:42 +02:00
-- TODO: special handling for builder node inventories
-- Ensure node metadata fields are all set, too
for field , value in pairs ( data.meta . fields ) do
2019-08-20 08:29:26 +02:00
node_meta : set_string ( field , value )
2019-08-18 21:02:42 +02:00
end
-- Clear digtron_id, this node is no longer part of an active digtron
2019-08-20 08:29:26 +02:00
node_meta : set_string ( " digtron_id " , " " )
2019-08-18 21:02:42 +02:00
end
end
2019-08-18 10:23:08 +02:00
2019-08-23 05:16:08 +02:00
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 } )
2019-08-18 10:23:08 +02:00
dispose_id ( digtron_id )
2019-08-23 05:16:08 +02:00
2019-08-20 08:29:26 +02:00
return root_pos
2019-08-18 21:02:42 +02:00
end
2019-08-19 02:23:51 +02:00
-- Removes the in-world nodes of a digtron
-- Does not destroy its layout info
2019-08-23 05:16:08 +02:00
-- returns a table of vectors of all the nodes that were removed
2019-08-24 07:53:38 +02:00
digtron.remove_from_world = function ( digtron_id , player_name )
2019-08-19 02:23:51 +02:00
local layout = retrieve_layout ( digtron_id )
2019-08-24 07:53:38 +02:00
local root_pos = retrieve_pos ( digtron_id )
2019-08-19 07:11:24 +02:00
if not layout then
2019-08-26 04:09:09 +02:00
minetest.log ( " error " , " [Digtron] digtron.remove_from_world Unable to find layout record for " .. digtron_id
2019-08-19 07:11:24 +02:00
.. " , wiping any remaining metadata for this id to prevent corruption. Sorry! " )
2019-08-24 07:53:38 +02:00
if root_pos then
local meta = minetest.get_meta ( root_pos )
meta : set_string ( " digtron_id " , " " )
end
2019-08-19 07:11:24 +02:00
dispose_id ( digtron_id )
2019-08-23 05:16:08 +02:00
return { }
2019-08-19 07:11:24 +02:00
end
2019-08-24 07:53:38 +02:00
if not root_pos then
2019-08-26 04:09:09 +02:00
minetest.log ( " error " , " [Digtron] digtron.remove_from_world Unable to find position for " .. digtron_id
2019-08-24 07:53:38 +02:00
.. " , it may have already been removed from the world. " )
return { }
end
2019-08-19 02:23:51 +02:00
local root_hash = minetest.hash_node_position ( root_pos )
local nodes_to_destroy = { }
for hash , data in pairs ( layout ) do
2019-08-25 19:15:50 +02:00
local node_pos = get_valid_data ( digtron_id , root_hash , hash , data , " digtron.destroy " )
2019-08-20 08:29:26 +02:00
if node_pos then
table.insert ( nodes_to_destroy , node_pos )
2019-08-19 02:23:51 +02:00
end
end
-- TODO: voxelmanip might be better here?
2019-08-23 05:16:08 +02:00
minetest.bulk_set_node ( nodes_to_destroy , { name = " air " } )
dispose_pos ( digtron_id )
return nodes_to_destroy
2019-08-19 02:23:51 +02:00
end
2019-08-21 02:58:32 +02:00
-- Tests if a Digtron can be built at the designated location
2019-08-21 09:21:03 +02:00
digtron.is_buildable_to = function ( digtron_id , root_pos , player_name , ignore_nodes , return_immediately_on_failure )
2019-08-19 02:23:51 +02:00
local layout = retrieve_layout ( digtron_id )
2019-08-23 05:16:08 +02:00
-- 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_pos = retrieve_pos ( digtron_id )
2019-08-25 23:52:35 +02:00
local ignore_hashes = { }
2019-08-23 05:16:08 +02:00
if old_pos then
local old_root_hash = minetest.hash_node_position ( old_pos )
for layout_hash , _ in pairs ( layout ) do
2019-08-25 23:52:35 +02:00
ignore_hashes [ layout_hash + old_root_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
2019-08-23 05:16:08 +02:00
end
end
2019-08-19 02:23:51 +02:00
local root_hash = minetest.hash_node_position ( root_pos )
2019-08-21 09:21:03 +02:00
local succeeded = { }
local failed = { }
2019-08-19 02:23:51 +02:00
local permitted = true
2019-08-21 09:21:03 +02:00
2019-08-23 05:16:08 +02:00
for layout_hash , data in pairs ( layout ) do
2019-08-25 19:15:50 +02:00
-- Don't use get_valid_data, the Digtron isn't in-world yet
2019-08-24 08:04:19 +02:00
local node_hash = layout_hash + root_hash
2019-08-23 05:16:08 +02:00
local node_pos = minetest.get_position_from_hash ( node_hash )
2019-08-20 08:29:26 +02:00
local node = minetest.get_node ( node_pos )
2019-08-19 02:23:51 +02:00
local node_def = minetest.registered_nodes [ node.name ]
-- TODO: lots of testing needed here
2019-08-25 19:15:50 +02:00
if not (
( node_def and node_def.buildable_to )
2019-08-25 23:52:35 +02:00
or ignore_hashes [ node_hash ] ) or
2019-08-26 01:16:23 +02:00
minetest.is_protected ( node_pos , player_name )
2019-08-25 19:15:50 +02:00
then
2019-08-21 09:21:03 +02:00
if return_immediately_on_failure then
return false -- no need to test further, don't return node positions
2019-08-21 02:58:32 +02:00
else
permitted = false
2019-08-21 09:21:03 +02:00
table.insert ( failed , node_pos )
2019-08-21 02:58:32 +02:00
end
2019-08-21 09:21:03 +02:00
elseif not return_immediately_on_failure then
table.insert ( succeeded , node_pos )
end
end
return permitted , succeeded , failed
end
2019-08-21 02:58:32 +02:00
-- Places the Digtron into the world.
digtron.build_to_world = function ( digtron_id , root_pos , player_name )
local layout = retrieve_layout ( digtron_id )
local root_hash = minetest.hash_node_position ( root_pos )
2019-08-21 09:21:03 +02:00
2019-08-21 02:58:32 +02:00
for hash , data in pairs ( layout ) do
2019-08-25 19:15:50 +02:00
-- Don't use get_valid_data, the Digtron isn't in-world yet
2019-08-24 08:04:19 +02:00
local node_pos = minetest.get_position_from_hash ( hash + root_hash )
2019-08-21 02:58:32 +02:00
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 )
2019-08-19 02:23:51 +02:00
end
2019-08-21 02:58:32 +02:00
meta : set_string ( " digtron_id " , digtron_id )
meta : mark_as_private ( " digtron_id " )
2019-08-19 02:23:51 +02:00
end
2019-08-21 02:58:32 +02:00
persist_pos ( digtron_id , root_pos )
2019-08-19 02:23:51 +02:00
2019-08-21 02:58:32 +02:00
return true
2019-08-19 02:23:51 +02:00
end
2019-08-21 09:21:03 +02:00
digtron.move = function ( digtron_id , dest_pos , player_name )
2019-08-23 05:16:08 +02:00
local permitted , succeeded , failed = digtron.is_buildable_to ( digtron_id , dest_pos , player_name )
if permitted then
2019-08-24 07:53:38 +02:00
local removed = digtron.remove_from_world ( digtron_id , player_name )
2019-08-23 05:16:08 +02:00
digtron.build_to_world ( digtron_id , 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
else
digtron.show_buildable_nodes ( { } , failed )
2019-08-24 07:53:38 +02:00
minetest.sound_play ( " digtron_squeal " , { gain = 0.5 , pos = dest_pos } )
2019-08-23 05:16:08 +02:00
end
2019-08-21 09:21:03 +02:00
end
2019-08-25 23:52:35 +02:00
local predict_dig = function ( digtron_id , player_name )
local predictive_inv = get_predictive_inventory ( digtron_id )
2019-08-24 07:53:38 +02:00
local layout = retrieve_layout ( digtron_id )
local root_pos = retrieve_pos ( digtron_id )
2019-08-25 23:52:35 +02:00
if not ( layout and root_pos and predictive_inv ) then
minetest.log ( " error " , " [Digtron] predict_dig failed to retrieve either "
.. " a predictive inventory, a layout, or a root position for " .. digtron_id )
return
end
2019-08-24 07:53:38 +02:00
local root_hash = minetest.hash_node_position ( root_pos )
2019-08-25 23:52:35 +02:00
local leftovers = { }
2019-08-24 07:53:38 +02:00
local dug_positions = { }
local cost = 0
2019-08-25 06:22:40 +02:00
for target_hash , digger_data in pairs ( retrieve_all_digger_targets ( digtron_id ) ) do
local target_pos = minetest.get_position_from_hash ( target_hash + root_hash )
local target_node = minetest.get_node ( target_pos )
local target_name = target_node.name
local targetdef = minetest.registered_nodes [ target_name ]
--TODO periodicity/offset test
2019-08-25 19:15:50 +02:00
if
target_name ~= " air " and -- TODO: generalise this somehow for liquids and other undiggables
minetest.get_item_group ( target_name , " digtron " ) == 0 and
2019-08-25 06:22:40 +02:00
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 minetest.is_protected ( target_pos , player_name )
2019-08-25 19:15:50 +02:00
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
2019-08-24 07:53:38 +02:00
2019-08-25 06:22:40 +02:00
local drops = minetest.get_node_drops ( target_name , " " )
for _ , drop in ipairs ( drops ) do
2019-08-25 23:52:35 +02:00
local leftover = predictive_inv : add_item ( " main " , ItemStack ( drop ) )
if leftover : get_count ( ) > 0 then
table.insert ( leftovers , leftover )
end
2019-08-24 07:53:38 +02:00
end
2019-08-25 06:22:40 +02:00
table.insert ( dug_positions , target_pos )
2019-08-24 07:53:38 +02:00
end
end
2019-08-25 23:52:35 +02:00
return leftovers , dug_positions , cost
2019-08-24 07:53:38 +02:00
end
2019-08-26 01:44:05 +02:00
-- Removes nodes and records node info so execute_dug_callbacks can be called later
2019-08-26 01:49:52 +02:00
local get_and_remove_nodes = function ( nodes_to_dig )
2019-08-26 01:16:23 +02:00
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
2019-08-26 01:49:52 +02:00
return ret
end
local log_dug_nodes = function ( nodes_to_dig , digtron_id , root_pos , player_name )
2019-08-26 01:44:05 +02:00
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 "
2019-08-26 01:49:52 +02:00
.. digtron_id .. " near " .. minetest.pos_to_string ( root_pos )
2019-08-26 01:44:05 +02:00
.. " operated by by " .. player_name )
end
2019-08-26 01:16:23 +02:00
end
2019-08-26 01:49:52 +02:00
local execute_dug_callbacks = function ( nodes_dug )
2019-08-26 01:44:05 +02:00
-- 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
2019-08-25 23:52:35 +02:00
digtron.execute_cycle = function ( digtron_id , player_name )
local leftovers , nodes_to_dig , cost = predict_dig ( digtron_id , player_name )
2019-08-26 01:16:23 +02:00
local old_root_pos = retrieve_pos ( digtron_id )
local root_node = minetest.get_node ( old_root_pos )
local new_root_pos = vector.add ( old_root_pos , cardinal_dirs [ facedir_to_dir_index ( root_node.param2 ) ] )
local buildable_to , succeeded , failed = digtron.is_buildable_to ( digtron_id , new_root_pos , player_name , nodes_to_dig )
2019-08-25 23:52:35 +02:00
if buildable_to then
2019-08-26 01:16:23 +02:00
digtron.fake_player : update ( old_root_pos , player_name )
2019-08-26 01:44:05 +02:00
-- Removing old nodes
local removed = digtron.remove_from_world ( digtron_id , player_name )
local nodes_dug = get_and_remove_nodes ( nodes_to_dig , player_name )
2019-08-26 01:49:52 +02:00
log_dug_nodes ( nodes_to_dig , digtron_id , old_root_pos , player_name )
2019-08-26 01:16:23 +02:00
2019-08-26 01:44:05 +02:00
-- Building new Digtron
2019-08-26 01:16:23 +02:00
digtron.build_to_world ( digtron_id , new_root_pos , player_name )
minetest.sound_play ( " digtron_construction " , { gain = 0.5 , pos = new_root_pos } )
2019-08-26 01:44:05 +02:00
-- TODO add build portion of the cycle
2019-08-26 01:16:23 +02:00
-- 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.
2019-08-25 23:52:35 +02:00
for _ , removed_pos in ipairs ( removed ) do
minetest.check_for_falling ( removed_pos )
end
2019-08-26 01:16:23 +02:00
-- Must be called after digtron.build_to_world because it triggers falling nodes
2019-08-26 01:44:05 +02:00
execute_dug_callbacks ( nodes_dug )
2019-08-26 01:16:23 +02:00
2019-08-25 23:52:35 +02:00
commit_predictive_inventory ( digtron_id )
else
2019-08-26 01:16:23 +02:00
clear_predictive_inventory ( digtron_id )
2019-08-25 23:52:35 +02:00
digtron.show_buildable_nodes ( { } , failed )
minetest.sound_play ( " digtron_squeal " , { gain = 0.5 , pos = new_pos } )
end
end
2019-08-18 21:02:42 +02:00
---------------------------------------------------------------------------------
2019-08-24 07:53:38 +02:00
-- Node callbacks
2019-08-18 21:02:42 +02:00
2019-08-20 04:56:28 +02:00
-- 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.
-- TODO: add protection check?
2019-08-18 21:02:42 +02:00
digtron.can_dig = function ( pos , digger )
local meta = minetest.get_meta ( pos )
2019-08-19 07:11:24 +02:00
local digtron_id = meta : get_string ( " digtron_id " )
2019-08-20 04:56:28 +02:00
if digtron_id == " " then
return true
2019-08-18 21:02:42 +02:00
end
2019-08-20 04:56:28 +02:00
local node = minetest.get_node ( pos )
2019-08-20 08:29:26 +02:00
local root_pos = retrieve_pos ( digtron_id )
2019-08-20 04:56:28 +02:00
local layout = retrieve_layout ( digtron_id )
2019-08-25 06:22:40 +02:00
if root_pos == nil or layout == nil then
2019-08-20 04:56:28 +02:00
-- Somehow, this belongs to a digtron id that's missing information that should exist in persistence.
local missing = " "
2019-08-20 08:29:26 +02:00
if root_pos == nil then missing = missing .. " root_pos " end
2019-08-20 04:56:28 +02:00
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 )
-- 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
2019-08-20 08:29:26 +02:00
local root_hash = minetest.hash_node_position ( root_pos )
2019-08-20 04:56:28 +02:00
local here_hash = minetest.hash_node_position ( pos )
2019-08-24 08:04:19 +02:00
local layout_hash = here_hash - root_hash
2019-08-20 04:56:28 +02:00
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
2019-08-18 21:02:42 +02:00
end
2019-08-20 04:02:16 +02:00
2019-08-20 04:56:28 +02:00
-- put this on all Digtron nodes. If other inventory types are added (eg, batteries)
-- update this.
2019-08-20 04:02:16 +02:00
digtron.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
2019-08-20 08:29:26 +02:00
if not digtron.disassemble ( digtron_id , " an explosion " ) then
2019-08-20 04:02:16 +02:00
minetest.log ( " error " , " [Digtron] a digtron node at " .. minetest.pos_to_string ( pos )
.. " was hit by an explosion and had digtron_id " .. digtron_id
2019-08-20 08:29:26 +02:00
.. " but didn't have a root position recorded, so it could not be disassembled. " )
2019-08-20 04:02:16 +02:00
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
2019-08-20 08:29:26 +02:00
2019-08-26 04:09:09 +02:00
-- 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.
digtron.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
2019-08-20 08:29:26 +02:00
------------------------------------------------------------------------------------
-- Creative trash
2019-08-25 19:15:50 +02:00
-- Catch when someone throws a Digtron controller with an ID into the trash, dispose
-- of the persisted layout.
2019-08-20 08:29:26 +02:00
if minetest.get_modpath ( " creative " ) then
2019-08-25 19:15:50 +02:00
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 )
2019-08-20 08:29:26 +02:00
end
2019-08-25 19:15:50 +02:00
trash.on_put = digtron_on_put
2019-08-20 08:29:26 +02:00
end
2019-08-25 19:15:50 +02:00
end
2019-08-20 08:29:26 +02:00
end