2016-12-31 07:38:18 +01:00
-- A random assortment of methods used in various places in this mod.
dofile ( minetest.get_modpath ( " digtron " ) .. " /util_item_place_node.lua " ) -- separated out to avoid potential for license complexity
2017-01-07 17:52:39 +01:00
dofile ( minetest.get_modpath ( " digtron " ) .. " /util_execute_cycle.lua " ) -- separated out simply for tidiness, there's some big code in there
2016-12-31 07:38:18 +01:00
2019-01-05 06:06:12 +01:00
local node_inventory_table = { type = " node " } -- a reusable parameter for get_inventory calls, set the pos parameter before using.
2017-01-04 07:56:34 +01:00
-- Apparently node_sound_metal_defaults is a newer thing, I ran into games using an older version of the default mod without it.
if default.node_sound_metal_defaults ~= nil then
digtron.metal_sounds = default.node_sound_metal_defaults ( )
else
digtron.metal_sounds = default.node_sound_stone_defaults ( )
end
2016-12-31 07:38:18 +01:00
digtron.find_new_pos = function ( pos , facing )
-- finds the point one node "forward", based on facing
local dir = minetest.facedir_to_dir ( facing )
2017-01-14 07:20:59 +01:00
return vector.add ( pos , dir )
end
2019-01-05 06:06:12 +01:00
local facedir_to_down_dir_table = {
[ 0 ] = { x = 0 , y =- 1 , z = 0 } ,
{ x = 0 , y = 0 , z =- 1 } ,
{ x = 0 , y = 0 , z = 1 } ,
{ x =- 1 , y = 0 , z = 0 } ,
{ x = 1 , y = 0 , z = 0 } ,
{ x = 0 , y = 1 , z = 0 }
}
2017-01-15 00:58:37 +01:00
digtron.facedir_to_down_dir = function ( facing )
2019-01-05 06:06:12 +01:00
return facedir_to_down_dir_table [ math.floor ( facing / 4 ) ]
2017-01-15 00:58:37 +01:00
end
2017-01-14 07:20:59 +01:00
2017-01-15 00:58:37 +01:00
digtron.find_new_pos_downward = function ( pos , facing )
return vector.add ( pos , digtron.facedir_to_down_dir ( facing ) )
2016-12-31 07:38:18 +01:00
end
2024-12-19 09:01:57 +01:00
local registered_nodes = core.registered_nodes
local unknown_node_def_fallback = {
-- for node_duplicator.lua / on_receive_fields
description = " unknown node " ,
-- for class_layout.lua / get_node_image
paramtype2 = " none " ,
}
digtron.get_nodedef = function ( name )
return registered_nodes [ name ] or unknown_node_def_fallback
end
2017-10-15 20:54:48 +02:00
digtron.mark_diggable = function ( pos , nodes_dug , player )
2016-12-31 07:38:18 +01:00
-- mark the node as dug, if the player provided would have been able to dig it.
-- Don't *actually* dig the node yet, though, because if we dig a node with sand over it the sand will start falling
-- and then destroy whatever node we place there subsequently (either by a builder head or by moving a digtron node)
-- I don't like sand. It's coarse and rough and irritating and it gets everywhere. And it necessitates complicated dig routines.
2017-01-02 01:12:32 +01:00
-- returns fuel cost and what will be dropped by digging these nodes.
2016-12-31 07:38:18 +01:00
local target = minetest.get_node ( pos )
2023-06-19 07:44:40 +02:00
2016-12-31 07:38:18 +01:00
-- prevent digtrons from being marked for digging.
2019-01-06 06:55:46 +01:00
if minetest.get_item_group ( target.name , " digtron " ) ~= 0 or
minetest.get_item_group ( target.name , " digtron_protected " ) ~= 0 or
minetest.get_item_group ( target.name , " immortal " ) ~= 0 then
2019-01-05 06:06:12 +01:00
return 0
2016-12-31 07:38:18 +01:00
end
local targetdef = minetest.registered_nodes [ target.name ]
2019-01-06 06:55:46 +01:00
if targetdef == nil or targetdef.can_dig == nil or targetdef.can_dig ( pos , player ) then
2016-12-31 07:38:18 +01:00
nodes_dug : set ( pos.x , pos.y , pos.z , true )
if target.name ~= " air " then
2017-01-02 01:12:32 +01:00
local in_known_group = false
local material_cost = 0
2023-06-19 07:44:40 +02:00
2017-09-12 08:20:52 +02:00
if digtron.config . uses_resources then
2017-01-05 03:23:21 +01:00
if minetest.get_item_group ( target.name , " cracky " ) ~= 0 then
in_known_group = true
2017-09-12 08:20:52 +02:00
material_cost = math.max ( material_cost , digtron.config . dig_cost_cracky )
2017-01-05 03:23:21 +01:00
end
if minetest.get_item_group ( target.name , " crumbly " ) ~= 0 then
in_known_group = true
2017-09-12 08:20:52 +02:00
material_cost = math.max ( material_cost , digtron.config . dig_cost_crumbly )
2017-01-05 03:23:21 +01:00
end
if minetest.get_item_group ( target.name , " choppy " ) ~= 0 then
in_known_group = true
2017-09-12 08:20:52 +02:00
material_cost = math.max ( material_cost , digtron.config . dig_cost_choppy )
2017-01-05 03:23:21 +01:00
end
if not in_known_group then
2017-09-12 08:20:52 +02:00
material_cost = digtron.config . dig_cost_default
2017-01-05 03:23:21 +01:00
end
2017-01-02 01:12:32 +01:00
end
2023-06-19 07:44:40 +02:00
2017-01-02 01:12:32 +01:00
return material_cost , minetest.get_node_drops ( target.name , " " )
2016-12-31 07:38:18 +01:00
end
end
2019-01-05 06:06:12 +01:00
return 0
2016-12-31 07:38:18 +01:00
end
2023-06-19 07:44:40 +02:00
2016-12-31 07:38:18 +01:00
digtron.can_build_to = function ( pos , protected_nodes , dug_nodes )
-- Returns whether a space is clear to have something put into it
if protected_nodes : get ( pos.x , pos.y , pos.z ) then
return false
end
-- tests if the location pointed to is clear to move something into
local target = minetest.get_node ( pos )
if target.name == " air " or
dug_nodes : get ( pos.x , pos.y , pos.z ) == true or
2024-12-19 09:01:57 +01:00
digtron.get_nodedef ( target.name ) . buildable_to
2016-12-31 07:38:18 +01:00
then
return true
end
return false
end
digtron.can_move_to = function ( pos , protected_nodes , dug_nodes )
-- Same as can_build_to, but also checks if the current node is part of the digtron.
-- this allows us to disregard obstructions that *will* move out of the way.
if digtron.can_build_to ( pos , protected_nodes , dug_nodes ) == true or
minetest.get_item_group ( minetest.get_node ( pos ) . name , " digtron " ) ~= 0 then
return true
end
return false
end
digtron.place_in_inventory = function ( itemname , inventory_positions , fallback_pos )
--tries placing the item in each inventory node in turn. If there's no room, drop it at fallback_pos
local itemstack = ItemStack ( itemname )
2019-01-05 06:06:12 +01:00
if inventory_positions ~= nil then
2023-06-19 07:44:40 +02:00
for _ , location in pairs ( inventory_positions ) do
2019-01-05 06:06:12 +01:00
node_inventory_table.pos = location.pos
local inv = minetest.get_inventory ( node_inventory_table )
itemstack = inv : add_item ( " main " , itemstack )
if itemstack : is_empty ( ) then
return nil
end
2016-12-31 07:38:18 +01:00
end
end
minetest.add_item ( fallback_pos , itemstack )
end
digtron.place_in_specific_inventory = function ( itemname , pos , inventory_positions , fallback_pos )
--tries placing the item in a specific inventory. Other parameters are used as fallbacks on failure
--Use this method for putting stuff back after testing and failed builds so that if the player
--is trying to keep various inventories organized manually stuff will go back where it came from,
--probably.
local itemstack = ItemStack ( itemname )
2019-01-05 06:06:12 +01:00
node_inventory_table.pos = pos
local inv = minetest.get_inventory ( node_inventory_table )
2016-12-31 07:38:18 +01:00
local returned_stack = inv : add_item ( " main " , itemstack )
if not returned_stack : is_empty ( ) then
-- we weren't able to put the item back into that particular inventory for some reason.
-- try putting it *anywhere.*
digtron.place_in_inventory ( returned_stack , inventory_positions , fallback_pos )
end
end
digtron.take_from_inventory = function ( itemname , inventory_positions )
2019-01-05 06:06:12 +01:00
if inventory_positions == nil then return nil end
2016-12-31 07:38:18 +01:00
--tries to take an item from each inventory node in turn. Returns location of inventory item was taken from on success, nil on failure
local itemstack = ItemStack ( itemname )
2023-06-19 07:44:40 +02:00
for _ , location in pairs ( inventory_positions ) do
2019-01-05 06:06:12 +01:00
node_inventory_table.pos = location.pos
local inv = minetest.get_inventory ( node_inventory_table )
2016-12-31 07:38:18 +01:00
local output = inv : remove_item ( " main " , itemstack )
if not output : is_empty ( ) then
2017-01-09 07:52:36 +01:00
return location.pos
2016-12-31 07:38:18 +01:00
end
end
return nil
end
2023-06-19 07:44:40 +02:00
-- 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
digtron.get_controlling_coordinate = function ( _ , facedir )
2016-12-31 07:38:18 +01:00
-- 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
2017-01-02 01:12:32 +01:00
end
2019-01-05 06:06:12 +01:00
local fuel_craft = { method = " fuel " , width = 1 , items = { } } -- reusable crafting recipe table for get_craft_result calls below
2023-06-19 07:44:40 +02:00
-- Searches fuel store inventories for burnable items and burns them until target is reached or surpassed
2017-10-10 21:02:35 +02:00
-- (or there's nothing left to burn). Returns the total fuel value burned
-- if the "test" parameter is set to true, doesn't actually take anything out of inventories.
-- We can get away with this sort of thing for fuel but not for builder inventory because there's just one
2017-01-02 01:12:32 +01:00
-- controller node burning stuff, not multiple build heads drawing from inventories in turn. Much simpler.
digtron.burn = function ( fuelstore_positions , target , test )
2019-01-05 06:06:12 +01:00
if fuelstore_positions == nil then
return 0
end
2017-01-02 01:12:32 +01:00
local current_burned = 0
2023-06-19 07:44:40 +02:00
for _ , location in pairs ( fuelstore_positions ) do
2017-01-02 01:12:32 +01:00
if current_burned > target then
break
end
2019-01-05 06:06:12 +01:00
node_inventory_table.pos = location.pos
local inv = minetest.get_inventory ( node_inventory_table )
2017-01-06 20:07:27 +01:00
local invlist = inv : get_list ( " fuel " )
2019-01-13 19:19:40 +01:00
if invlist == nil then -- This check shouldn't be needed, it's yet another guard against https://github.com/minetest/minetest/issues/8067
break
end
2023-06-19 07:44:40 +02:00
for _ , itemstack in pairs ( invlist ) do
2019-01-05 06:06:12 +01:00
fuel_craft.items [ 1 ] = itemstack : peek_item ( 1 )
local fuel_per_item = minetest.get_craft_result ( fuel_craft ) . time
2017-01-02 01:12:32 +01:00
if fuel_per_item ~= 0 then
local actual_burned = math.min (
2019-01-05 06:06:12 +01:00
math.ceil ( ( target - current_burned ) / fuel_per_item ) , -- burn this many, if we can.
2017-01-02 01:12:32 +01:00
itemstack : get_count ( ) -- how many we have at most.
)
if test ~= true then
-- don't bother recording the items if we're just testing, nothing is actually being removed.
itemstack : set_count ( itemstack : get_count ( ) - actual_burned )
end
current_burned = current_burned + actual_burned * fuel_per_item
end
if current_burned > target then
break
end
end
if test ~= true then
-- only update the list if we're doing this for real.
2017-01-06 20:07:27 +01:00
inv : set_list ( " fuel " , invlist )
2017-01-02 01:12:32 +01:00
end
end
return current_burned
2017-01-03 02:50:03 +01:00
end
2017-10-10 21:02:35 +02:00
-- Consume energy from the batteries
2023-06-19 07:44:40 +02:00
-- The same as burning coal, except that instead of destroying the items in the inventory, we merely drain
-- the charge in them, leaving them empty. The charge is converted into "coal heat units" by a downscaling
2017-10-10 21:02:35 +02:00
-- factor, since if taken at face value (10000 EU), the batteries would be the ultimate power source barely
-- ever needing replacement.
digtron.tap_batteries = function ( battery_positions , target , test )
2019-01-05 06:06:12 +01:00
if ( battery_positions == nil ) then
return 0
end
2017-10-10 21:02:35 +02:00
local current_burned = 0
-- 1 coal block is 370 PU
-- 1 coal lump is 40 PU
-- An RE battery holds 10000 EU of charge
-- local power_ratio = 100 -- How much charge equals 1 unit of PU from coal
-- setting Moved to digtron.config.power_ratio
2023-06-19 07:44:40 +02:00
for _ , location in pairs ( battery_positions ) do
2017-10-10 21:02:35 +02:00
if current_burned > target then
break
end
2019-01-05 06:06:12 +01:00
node_inventory_table.pos = location.pos
local inv = minetest.get_inventory ( node_inventory_table )
2017-10-10 21:02:35 +02:00
local invlist = inv : get_list ( " batteries " )
2023-06-19 07:44:40 +02:00
2019-01-13 19:19:40 +01:00
if ( invlist == nil ) then -- This check shouldn't be needed, it's yet another guard against https://github.com/minetest/minetest/issues/8067
2017-10-10 21:02:35 +02:00
break
end
2023-06-19 07:44:40 +02:00
for _ , itemstack in pairs ( invlist ) do
2017-10-10 21:02:35 +02:00
local meta = minetest.deserialize ( itemstack : get_metadata ( ) )
2017-10-11 04:33:56 +02:00
if ( meta ~= nil ) then
2017-10-10 21:02:35 +02:00
local power_available = math.floor ( meta.charge / digtron.config . power_ratio )
if power_available ~= 0 then
local actual_burned = power_available -- we just take all we have from the battery, since they aren't stackable
2020-06-01 11:22:28 +02:00
-- don't bother recording the items if we're just testing, nothing is actually being removed.
2017-10-10 21:02:35 +02:00
if test ~= true then
2020-06-01 11:22:28 +02:00
-- since we are taking everything, the wear and charge can both be set to 0
itemstack : set_wear ( 0 )
meta.charge = 0
2017-10-10 21:02:35 +02:00
itemstack : set_metadata ( minetest.serialize ( meta ) )
end
current_burned = current_burned + actual_burned
end
end
2023-06-19 07:44:40 +02:00
2017-10-10 21:02:35 +02:00
if current_burned > target then
break
end
end
2023-06-19 07:44:40 +02:00
2017-10-10 21:02:35 +02:00
if test ~= true then
-- only update the list if we're doing this for real.
inv : set_list ( " batteries " , invlist )
end
end
return current_burned
end
2017-01-03 02:50:03 +01:00
digtron.remove_builder_item = function ( pos )
2020-08-12 21:15:36 +02:00
local objects = minetest.get_objects_inside_radius ( pos , 0.5 )
2017-01-03 02:50:03 +01:00
if objects ~= nil then
for _ , obj in ipairs ( objects ) do
if obj and obj : get_luaentity ( ) and obj : get_luaentity ( ) . name == " digtron:builder_item " then
obj : remove ( )
end
end
end
end
digtron.update_builder_item = function ( pos )
digtron.remove_builder_item ( pos )
2019-01-05 06:06:12 +01:00
node_inventory_table.pos = pos
local inv = minetest.get_inventory ( node_inventory_table )
2019-08-18 22:19:19 +02:00
if inv == nil or inv : get_size ( " main " ) < 1 then return end
2017-01-03 02:50:03 +01:00
local item_stack = inv : get_stack ( " main " , 1 )
if not item_stack : is_empty ( ) then
digtron.create_builder_item = item_stack : get_name ( )
minetest.add_entity ( pos , " digtron:builder_item " )
end
2017-01-07 07:48:48 +01:00
end
2019-01-05 06:06:12 +01:00
local damage_def = {
full_punch_interval = 1.0 ,
damage_groups = { } ,
}
digtron.damage_creatures = function ( player , source_pos , target_pos , amount , items_dropped )
2020-02-25 20:42:57 +01:00
if type ( player ) ~= ' userdata ' then
return
end
2023-02-21 20:41:49 +01:00
local objects = minetest.get_objects_inside_radius ( target_pos , 1.0 )
2017-01-07 07:48:48 +01:00
if objects ~= nil then
2019-01-05 06:06:12 +01:00
damage_def.damage_groups . fleshy = amount
local velocity = {
x = target_pos.x - source_pos.x ,
y = target_pos.y - source_pos.y + 0.2 ,
z = target_pos.z - source_pos.z ,
}
2017-01-07 07:48:48 +01:00
for _ , obj in ipairs ( objects ) do
2019-01-05 06:06:12 +01:00
if obj : is_player ( ) then
2024-08-22 06:30:59 +02:00
-- Digtron moving logic handles owner movement
if obj : get_player_name ( ) ~= player : get_player_name ( ) then
-- See issue #2960 for status of a "set player velocity" method
-- instead, knock the player back
local newpos = {
x = target_pos.x + velocity.x ,
y = target_pos.y + velocity.y ,
z = target_pos.z + velocity.z ,
}
obj : set_pos ( newpos )
obj : punch ( player , 1.0 , damage_def , nil )
end
2019-01-05 06:06:12 +01:00
else
local lua_entity = obj : get_luaentity ( )
if lua_entity ~= nil then
if lua_entity.name == " __builtin:item " then
table.insert ( items_dropped , lua_entity.itemstring )
lua_entity.itemstring = " "
obj : remove ( )
2024-01-15 14:16:46 +01:00
elseif lua_entity.name == " __builtin:falling_node " then
-- Eat all falling nodes in front of the digtron
-- to avoid them eating away our digger heads
local drops = minetest.get_node_drops ( lua_entity.node , " " )
for _ , item in pairs ( drops ) do
table.insert ( items_dropped , item )
end
obj : remove ( )
2019-01-05 06:06:12 +01:00
else
if obj.add_velocity ~= nil then
obj : add_velocity ( velocity )
else
local vel = obj : get_velocity ( )
obj : set_velocity ( vector.add ( vel , velocity ) )
end
obj : punch ( player , 1.0 , damage_def , nil )
end
end
2017-01-07 07:48:48 +01:00
end
end
end
2019-01-05 06:06:12 +01:00
-- If we killed any mobs they might have dropped some stuff, vacuum that up now too.
2023-02-21 20:41:49 +01:00
objects = minetest.get_objects_inside_radius ( target_pos , 1.0 )
2019-01-05 06:06:12 +01:00
if objects ~= nil then
for _ , obj in ipairs ( objects ) do
if not obj : is_player ( ) then
local lua_entity = obj : get_luaentity ( )
if lua_entity ~= nil and lua_entity.name == " __builtin:item " then
table.insert ( items_dropped , lua_entity.itemstring )
lua_entity.itemstring = " "
obj : remove ( )
end
end
2023-06-19 07:44:40 +02:00
end
2019-01-05 06:06:12 +01:00
end
2017-01-07 07:48:48 +01:00
end
2017-01-14 07:20:59 +01:00
digtron.is_soft_material = function ( target )
local target_node = minetest.get_node ( target )
if minetest.get_item_group ( target_node.name , " crumbly " ) ~= 0 or
minetest.get_item_group ( target_node.name , " choppy " ) ~= 0 or
minetest.get_item_group ( target_node.name , " snappy " ) ~= 0 or
minetest.get_item_group ( target_node.name , " oddly_breakable_by_hand " ) ~= 0 or
minetest.get_item_group ( target_node.name , " fleshy " ) ~= 0 then
return true
end
return false
2017-09-20 07:25:11 +02:00
end
2019-06-10 07:53:09 +02:00
-- If someone sets very large offsets or intervals for the offset markers they might be added too far
-- away. safe_add_entity causes these attempts to be ignored rather than crashing the game.
-- returns the entity if successful, nil otherwise
2023-06-19 07:44:40 +02:00
local function safe_add_entity ( pos , name )
local success , ret = pcall ( minetest.add_entity , pos , name )
2019-06-10 07:53:09 +02:00
if success then return ret else return nil end
end
2017-09-20 07:25:11 +02:00
digtron.show_offset_markers = function ( pos , offset , period )
local buildpos = digtron.find_new_pos ( pos , minetest.get_node ( pos ) . param2 )
2023-11-02 11:29:50 +01:00
local x_pos = math.floor ( ( buildpos.x + offset ) / period ) * period - ( offset or 0 )
2019-06-10 07:53:09 +02:00
safe_add_entity ( { x = x_pos , y = buildpos.y , z = buildpos.z } , " digtron:marker " )
2017-09-20 07:25:11 +02:00
if x_pos >= buildpos.x then
2019-06-10 07:53:09 +02:00
safe_add_entity ( { x = x_pos - period , y = buildpos.y , z = buildpos.z } , " digtron:marker " )
2017-09-20 07:25:11 +02:00
end
if x_pos <= buildpos.x then
2019-06-10 07:53:09 +02:00
safe_add_entity ( { x = x_pos + period , y = buildpos.y , z = buildpos.z } , " digtron:marker " )
2017-09-20 07:25:11 +02:00
end
local y_pos = math.floor ( ( buildpos.y + offset ) / period ) * period - offset
2019-06-10 07:53:09 +02:00
safe_add_entity ( { x = buildpos.x , y = y_pos , z = buildpos.z } , " digtron:marker_vertical " )
2017-09-20 07:25:11 +02:00
if y_pos >= buildpos.y then
2019-06-10 07:53:09 +02:00
safe_add_entity ( { x = buildpos.x , y = y_pos - period , z = buildpos.z } , " digtron:marker_vertical " )
2017-09-20 07:25:11 +02:00
end
if y_pos <= buildpos.y then
2019-06-10 07:53:09 +02:00
safe_add_entity ( { x = buildpos.x , y = y_pos + period , z = buildpos.z } , " digtron:marker_vertical " )
2017-09-20 07:25:11 +02:00
end
local z_pos = math.floor ( ( buildpos.z + offset ) / period ) * period - offset
2019-06-10 07:53:09 +02:00
local entity = safe_add_entity ( { x = buildpos.x , y = buildpos.y , z = z_pos } , " digtron:marker " )
2024-06-25 21:31:20 +02:00
if entity ~= nil then entity : set_yaw ( 1.5708 ) end
2023-06-19 07:44:40 +02:00
2017-09-20 07:25:11 +02:00
if z_pos >= buildpos.z then
2023-06-19 07:44:40 +02:00
entity = safe_add_entity ( { x = buildpos.x , y = buildpos.y , z = z_pos - period } , " digtron:marker " )
2024-06-25 21:31:20 +02:00
if entity ~= nil then entity : set_yaw ( 1.5708 ) end
2017-09-20 07:25:11 +02:00
end
if z_pos <= buildpos.z then
2023-06-19 07:44:40 +02:00
entity = safe_add_entity ( { x = buildpos.x , y = buildpos.y , z = z_pos + period } , " digtron:marker " )
2024-06-25 21:31:20 +02:00
if entity ~= nil then entity : set_yaw ( 1.5708 ) end
2017-09-20 07:25:11 +02:00
end
2020-02-25 20:42:57 +01:00
end
2024-08-22 06:32:24 +02:00
digtron.check_protected_and_record = function ( pos , player )
local name = player : get_player_name ( )
if minetest.is_protected ( pos , name ) then
minetest.record_protection_violation ( pos , name )
return true
end
return false
end
digtron.protected_allow_metadata_inventory_put = function ( pos , _ , _ , stack , player )
return digtron.check_protected_and_record ( pos , player ) and 0 or stack : get_count ( )
end
digtron.protected_allow_metadata_inventory_move = function ( pos , _ , _ , _ , _ , count , player )
return digtron.check_protected_and_record ( pos , player ) and 0 or count
end
digtron.protected_allow_metadata_inventory_take = function ( pos , _ , _ , stack , player )
return digtron.check_protected_and_record ( pos , player ) and 0 or stack : get_count ( )
end