2019-08-18 07:12:12 +02:00
local mod_meta = minetest.get_mod_storage ( )
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-18 10:23:08 +02:00
------------------------------------------------------------------------------------
-- Inventory
2019-08-18 23:29:15 +02:00
-- indexed by digtron_id, set to true whenever the detached inventory's contents change
2019-08-18 10:23:08 +02:00
local dirty_inventories = { }
2019-08-18 07:12:12 +02:00
local detached_inventory_callbacks = {
2019-08-18 21:02:42 +02:00
-- Called when a player wants to move items inside the inventory.
-- Return value: number of items allowed to move.
allow_move = function ( inv , from_list , from_index , to_list , to_index , count , player )
--allow anything in "main"
if to_list == " main " then
return count
end
--only allow fuel items in "fuel"
local stack = inv : get_stack ( from_list , from_index )
if minetest.get_craft_result ( { method = " fuel " , width = 1 , items = { stack } } ) . time ~= 0 then
return stack : get_count ( )
end
return 0
end ,
-- Called when a player wants to put something into the inventory.
-- Return value: number of items allowed to put.
-- Return value -1: Allow and don't modify item count in inventory.
allow_put = function ( inv , listname , index , stack , player )
-- Only allow fuel items to be placed in fuel
if listname == " fuel " then
2019-08-18 07:12:12 +02:00
if minetest.get_craft_result ( { method = " fuel " , width = 1 , items = { stack } } ) . time ~= 0 then
return stack : get_count ( )
2019-08-18 21:02:42 +02:00
else
return 0
2019-08-18 07:12:12 +02:00
end
2019-08-18 21:02:42 +02:00
end
return stack : get_count ( ) -- otherwise, allow all drops
end ,
2019-08-18 07:12:12 +02:00
2019-08-18 21:02:42 +02:00
-- Called when a player wants to take something out of the inventory.
-- Return value: number of items allowed to take.
-- Return value -1: Allow and don't modify item count in inventory.
allow_take = function ( inv , listname , index , stack , player )
return stack : get_count ( )
end ,
2019-08-18 07:12:12 +02:00
2019-08-18 21:02:42 +02:00
-- Called after the actual action has happened, according to what was
-- allowed.
-- No return value.
on_move = function ( inv , from_list , from_index , to_list , to_index , count , player )
dirty_inventories [ inv : get_location ( ) . name ] = true
end ,
on_put = function ( inv , listname , index , stack , player )
dirty_inventories [ inv : get_location ( ) . name ] = true
end ,
on_take = function ( inv , listname , index , stack , player )
dirty_inventories [ inv : get_location ( ) . name ] = true
end ,
}
2019-08-18 07:12:12 +02:00
2019-08-18 10:23:08 +02:00
-- If the detached inventory doesn't exist, reads saved metadata version of the inventory and creates it
-- Doesn't do anything if the detached inventory already exists, the detached inventory is authoritative
2019-08-18 23:29:15 +02:00
digtron.retrieve_inventory = function ( digtron_id )
local inv = minetest.get_inventory ( { type = " detached " , name = digtron_id } )
2019-08-18 10:23:08 +02:00
if inv == nil then
2019-08-18 23:29:15 +02:00
inv = minetest.create_detached_inventory ( digtron_id , detached_inventory_callbacks )
2019-08-19 07:11:24 +02:00
local inv_string = mod_meta : get_string ( digtron_id .. " :inv " )
2019-08-18 10:23:08 +02:00
if inv_string ~= " " then
local inventory_table = minetest.deserialize ( inv_string )
for listname , invlist in pairs ( inventory_table ) do
inv : set_size ( listname , # invlist )
inv : set_list ( listname , invlist )
end
end
end
2019-08-18 21:02:42 +02:00
return inv
2019-08-18 10:23:08 +02:00
end
2019-08-18 21:02:42 +02:00
-- Stores contents of detached inventory as a metadata string
2019-08-18 23:29:15 +02:00
local persist_inventory = function ( digtron_id )
local inv = minetest.get_inventory ( { type = " detached " , name = digtron_id } )
2019-08-18 10:23:08 +02:00
if inv == nil then
minetest.log ( " error " , " [Digtron] persist_inventory attempted to record a nonexistent inventory "
2019-08-18 23:29:15 +02:00
.. digtron_id )
2019-08-18 10:23:08 +02:00
return
end
local lists = inv : get_lists ( )
local persist = { }
for listname , invlist in pairs ( lists ) do
local inventory = { }
for i , stack in ipairs ( invlist ) do
table.insert ( inventory , stack : to_string ( ) ) -- convert into strings for serialization
end
persist [ listname ] = inventory
end
2019-08-19 07:11:24 +02:00
mod_meta : set_string ( digtron_id .. " :inv " , minetest.serialize ( persist ) )
2019-08-18 07:12:12 +02:00
end
2019-08-18 10:23:08 +02:00
minetest.register_globalstep ( function ( dtime )
2019-08-18 23:29:15 +02:00
for digtron_id , _ in pairs ( dirty_inventories ) do
persist_inventory ( digtron_id )
dirty_inventories [ digtron_id ] = nil
2019-08-18 10:23:08 +02:00
end
end )
--------------------------------------------------------------------------------------
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-18 23:29:15 +02:00
local inv = minetest.create_detached_inventory ( digtron_id , detached_inventory_callbacks )
return digtron_id , inv
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
-- Given a facedir, returns an index into either cardinal_dirs or cardinal_dirs_hash that
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
-- don't record details, the content of this node will change as the digtron moves
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
adjacent_to_diggers [ potential_target ] = { periodicity = fields.periodicity , offset = fields.offset }
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-18 21:02:42 +02:00
local digtron_adjacent = { } -- Nodes that are adjacent to Digtron but not a part of it
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-18 23:29:15 +02:00
local digtron_id , digtron_inv = create_new_id ( root_pos )
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
minetest.log ( " error " , " digtron.disassemble was unable to find a position for " .. digtron_id
.. " , 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-20 08:29:26 +02:00
minetest.log ( " error " , " digtron.disassemble was unable to find either layout or inventory record for " .. digtron_id
.. " , disassembly was impossible. Clearing any other remaining data for this id. " )
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-18 21:02:42 +02:00
for i , itemstack in ipairs ( inv : get_list ( listname ) ) do
-- add everything, putting leftovers back in the main inventory
2019-08-20 08:29:26 +02:00
inv : set_stack ( listname , i , node_inv : add_item ( listname , itemstack ) )
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-24 07:53:38 +02:00
minetest.log ( " error " , " 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
minetest.log ( " error " , " digtron.remove_from_world Unable to find position for " .. digtron_id
.. " , 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-20 08:29:26 +02:00
local node_pos , node , node_meta = get_valid_data ( digtron_id , root_hash , hash , data , " digtron.destroy " )
if node_pos then
table.insert ( nodes_to_destroy , node_pos )
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-23 05:16:08 +02:00
--TODO implement ignore_nodes, needed for ignoring nodes that have been flagged as dug
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 )
local old_hashes = { }
if old_pos then
local old_root_hash = minetest.hash_node_position ( old_pos )
for layout_hash , _ in pairs ( layout ) do
2019-08-24 08:04:19 +02:00
old_hashes [ layout_hash + old_root_hash ] = 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-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-23 05:16:08 +02:00
if not ( ( node_def and node_def.buildable_to ) or old_hashes [ node_hash ] ) 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-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 )
minetest.chat_send_all ( " move attempt " )
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-24 07:53:38 +02:00
digtron.predict_dig = function ( digtron_id , player_name )
local layout = retrieve_layout ( digtron_id )
local root_pos = retrieve_pos ( digtron_id )
2019-08-24 08:04:19 +02:00
if not ( layout and root_pos ) then return end -- TODO error messages etc
2019-08-24 07:53:38 +02:00
local root_hash = minetest.hash_node_position ( root_pos )
local products = { }
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
if 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 minetest.is_protected ( target_pos , player_name )
then
2019-08-24 07:53:38 +02:00
-- TODO: move this into some kind of shared definition
--if digtron.config.uses_resources then
-- 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
2019-08-25 06:22:40 +02:00
local drops = minetest.get_node_drops ( target_name , " " )
for _ , drop in ipairs ( drops ) do
products [ drop ] = ( products [ drop ] or 0 ) + 1
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
return products , dug_positions , cost
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
------------------------------------------------------------------------------------
-- Creative trash
-- This is wrapped in an after() call as a workaround for to https://github.com/minetest/minetest/issues/8827
if minetest.get_modpath ( " creative " ) then
minetest.after ( 1 , function ( )
if minetest.get_inventory ( { type = " detached " , name = " creative_trash " } ) then
if minetest.remove_detached_inventory ( " creative_trash " ) then
-- Create the trash field
local trash = minetest.create_detached_inventory ( " creative_trash " , {
-- Allow the stack to be placed and remove it in on_put()
-- This allows the creative inventory to restore the stack
allow_put = function ( inv , listname , index , stack , player )
return stack : get_count ( )
end ,
on_put = function ( inv , listname , index , stack , player )
local stack = inv : get_stack ( listname , index )
local stack_meta = stack : get_meta ( )
local digtron_id = stack_meta : get_string ( " digtron_id " )
if digtron_id ~= " " then
minetest.log ( " action " , player : get_player_name ( ) .. " disposed of " .. digtron_id
.. " in the creative inventory's trash receptacle. " )
dispose_id ( digtron_id )
end
inv : set_list ( listname , { } )
end ,
} )
trash : set_size ( " main " , 1 )
end
end
end )
end