2017-02-11 04:26:00 +01:00
mcl_util = { }
-- Based on minetest.rotate_and_place
--[[
Attempt to predict the desired orientation of the pillar - like node
defined by ` itemstack ` , and place it accordingly in one of 3 possible
orientations ( X , Y or Z ) .
Stacks are handled normally if the ` infinitestacks `
field is false or omitted ( else , the itemstack is not changed ) .
* ` invert_wall ` : if ` true ` , place wall - orientation on the ground and ground -
orientation on wall
This function is a simplified version of minetest.rotate_and_place .
The Minetest function is seen as inappropriate because this includes mirror
images of possible orientations , causing problems with pillar shadings .
] ]
2017-02-11 04:33:06 +01:00
function mcl_util . rotate_axis_and_place ( itemstack , placer , pointed_thing , infinitestacks , invert_wall )
2017-02-11 04:26:00 +01:00
local unode = minetest.get_node_or_nil ( pointed_thing.under )
if not unode then
return
end
local undef = minetest.registered_nodes [ unode.name ]
if undef and undef.on_rightclick then
undef.on_rightclick ( pointed_thing.under , unode , placer ,
itemstack , pointed_thing )
return
end
local fdir = minetest.dir_to_facedir ( placer : get_look_dir ( ) )
local wield_name = itemstack : get_name ( )
local above = pointed_thing.above
local under = pointed_thing.under
local is_x = ( above.x ~= under.x )
local is_y = ( above.y ~= under.y )
local is_z = ( above.z ~= under.z )
local anode = minetest.get_node_or_nil ( above )
if not anode then
return
end
local pos = pointed_thing.above
local node = anode
if undef and undef.buildable_to then
pos = pointed_thing.under
node = unode
end
if minetest.is_protected ( pos , placer : get_player_name ( ) ) then
minetest.record_protection_violation ( pos , placer : get_player_name ( ) )
return
end
local ndef = minetest.registered_nodes [ node.name ]
if not ndef or not ndef.buildable_to then
return
end
local p2
if is_y then
if invert_wall then
if fdir == 3 or fdir == 1 then
p2 = 12
else
p2 = 6
end
end
elseif is_x then
if invert_wall then
p2 = 0
else
p2 = 12
end
elseif is_z then
if invert_wall then
p2 = 0
else
p2 = 6
end
end
minetest.set_node ( pos , { name = wield_name , param2 = p2 } )
if not infinitestacks then
itemstack : take_item ( )
return itemstack
end
end
2017-02-11 04:33:06 +01:00
-- Wrapper of above function for use as `on_place` callback (Recommended).
-- Similar to minetest.rotate_node.
function mcl_util . rotate_axis ( itemstack , placer , pointed_thing )
mcl_util.rotate_axis_and_place ( itemstack , placer , pointed_thing ,
2020-07-10 16:08:40 +02:00
minetest.is_creative_enabled ( placer : get_player_name ( ) ) ,
2017-02-11 04:33:06 +01:00
placer : get_player_control ( ) . sneak )
return itemstack
end
2017-08-03 23:27:22 +02:00
-- Returns position of the neighbor of a double chest node
-- or nil if node is invalid.
-- This function assumes that the large chest is actually intact
-- * pos: Position of the node to investigate
-- * param2: param2 of that node
-- * side: Which "half" the investigated node is. "left" or "right"
function mcl_util . get_double_container_neighbor_pos ( pos , param2 , side )
if side == " right " then
if param2 == 0 then
return { x = pos.x - 1 , y = pos.y , z = pos.z }
elseif param2 == 1 then
return { x = pos.x , y = pos.y , z = pos.z + 1 }
elseif param2 == 2 then
return { x = pos.x + 1 , y = pos.y , z = pos.z }
elseif param2 == 3 then
return { x = pos.x , y = pos.y , z = pos.z - 1 }
end
else
if param2 == 0 then
return { x = pos.x + 1 , y = pos.y , z = pos.z }
elseif param2 == 1 then
return { x = pos.x , y = pos.y , z = pos.z - 1 }
elseif param2 == 2 then
return { x = pos.x - 1 , y = pos.y , z = pos.z }
elseif param2 == 3 then
return { x = pos.x , y = pos.y , z = pos.z + 1 }
end
end
end
2017-08-04 03:00:33 +02:00
-- Iterates through all items in the given inventory and
-- returns the slot of the first item which matches a condition.
-- Returns nil if no item was found.
--- source_inventory: Inventory to take the item from
--- source_list: List name of the source inventory from which to take the item
--- destination_inventory: Put item into this inventory
--- destination_list: List name of the destination inventory to which to put the item into
--- condition: Function which takes an itemstack and returns true if it matches the desired item condition.
--- If set to nil, the slot of the first item stack will be taken unconditionally.
-- dst_inventory and dst_list can also be nil if condition is nil.
function mcl_util . get_eligible_transfer_item_slot ( src_inventory , src_list , dst_inventory , dst_list , condition )
local size = src_inventory : get_size ( src_list )
local stack
for i = 1 , size do
stack = src_inventory : get_stack ( src_list , i )
if not stack : is_empty ( ) and ( condition == nil or condition ( stack , src_inventory , src_list , dst_inventory , dst_list ) ) then
return i
end
end
return nil
end
2018-05-12 21:50:56 +02:00
-- Returns true if itemstack is a shulker box
2017-08-04 03:00:33 +02:00
local is_not_shulker_box = function ( itemstack )
local g = minetest.get_item_group ( itemstack : get_name ( ) , " shulker_box " )
return g == 0 or g == nil
end
2017-08-03 23:23:33 +02:00
-- Moves a single item from one inventory to another.
2017-02-14 00:44:23 +01:00
--- source_inventory: Inventory to take the item from
--- source_list: List name of the source inventory from which to take the item
2017-02-14 01:25:02 +01:00
--- source_stack_id: The inventory position ID of the source inventory to take the item from (-1 for first occupied slot)
2017-02-14 00:44:23 +01:00
--- destination_inventory: Put item into this inventory
--- destination_list: List name of the destination inventory to which to put the item into
2017-02-14 00:10:37 +01:00
-- Returns true on success and false on failure
2017-02-14 00:44:23 +01:00
-- Possible failures: No item in source slot, destination inventory full
2017-02-14 00:10:37 +01:00
function mcl_util . move_item ( source_inventory , source_list , source_stack_id , destination_inventory , destination_list )
2017-02-14 01:25:02 +01:00
if source_stack_id == - 1 then
source_stack_id = mcl_util.get_first_occupied_inventory_slot ( source_inventory , source_list )
if source_stack_id == nil then
return false
end
end
2017-02-14 00:10:37 +01:00
if not source_inventory : is_empty ( source_list ) then
local stack = source_inventory : get_stack ( source_list , source_stack_id )
if not stack : is_empty ( ) then
2019-04-05 15:30:32 +02:00
local new_stack = ItemStack ( stack )
new_stack : set_count ( 1 )
if not destination_inventory : room_for_item ( destination_list , new_stack ) then
2017-02-14 00:10:37 +01:00
return false
end
stack : take_item ( )
source_inventory : set_stack ( source_list , source_stack_id , stack )
2019-04-05 15:30:32 +02:00
destination_inventory : add_item ( destination_list , new_stack )
2017-02-14 00:10:37 +01:00
return true
end
end
return false
end
2017-02-14 00:44:23 +01:00
2017-08-03 23:23:33 +02:00
-- Moves a single item from one container node into another. Performs a variety of high-level
-- checks to prevent invalid transfers such as shulker boxes into shulker boxes
2017-02-14 00:44:23 +01:00
--- source_pos: Position ({x,y,z}) of the node to take the item from
--- destination_pos: Position ({x,y,z}) of the node to put the item into
2017-08-04 02:19:47 +02:00
--- source_list (optional): List name of the source inventory from which to take the item. Default is normally "main"; "dst" for furnace
2017-08-04 03:00:33 +02:00
--- source_stack_id (optional): The inventory position ID of the source inventory to take the item from (-1 for slot of the first valid item; -1 is default)
2017-08-04 02:19:47 +02:00
--- destination_list (optional): List name of the destination inventory. Default is normally "main"; "src" for furnace
-- Returns true on success and false on failure.
function mcl_util . move_item_container ( source_pos , destination_pos , source_list , source_stack_id , destination_list )
2017-08-03 23:23:33 +02:00
local dpos = table.copy ( destination_pos )
2017-08-04 03:34:28 +02:00
local spos = table.copy ( source_pos )
local snode = minetest.get_node ( spos )
local dnode = minetest.get_node ( dpos )
2017-08-03 23:23:33 +02:00
local dctype = minetest.get_item_group ( dnode.name , " container " )
2017-08-04 02:19:47 +02:00
local sctype = minetest.get_item_group ( snode.name , " container " )
2017-08-03 23:23:33 +02:00
2018-05-12 22:48:49 +02:00
-- Container type 7 does not allow any movement
if sctype == 7 then
return false
end
2017-08-03 23:23:33 +02:00
-- Normalize double container by forcing to always use the left segment first
2017-08-04 03:34:28 +02:00
local normalize_double_container = function ( pos , node , ctype )
if ctype == 6 then
pos = mcl_util.get_double_container_neighbor_pos ( pos , node.param2 , " right " )
if not pos then
return false
end
node = minetest.get_node ( pos )
ctype = minetest.get_item_group ( node.name , " container " )
-- The left segment seems incorrect? We better bail out!
if ctype ~= 5 then
return false
end
2017-08-03 23:23:33 +02:00
end
2017-08-04 03:34:28 +02:00
return pos , node , ctype
2017-08-03 23:23:33 +02:00
end
2017-08-04 03:34:28 +02:00
spos , snode , sctype = normalize_double_container ( spos , snode , sctype )
dpos , dnode , dctype = normalize_double_container ( dpos , dnode , dctype )
if not spos or not dpos then return false end
local smeta = minetest.get_meta ( spos )
2017-08-03 23:23:33 +02:00
local dmeta = minetest.get_meta ( dpos )
2017-02-14 00:44:23 +01:00
local sinv = smeta : get_inventory ( )
local dinv = dmeta : get_inventory ( )
2017-08-04 01:58:31 +02:00
-- Default source lists
if not source_list then
-- Main inventory for most container types
2018-05-12 21:50:56 +02:00
if sctype == 2 or sctype == 3 or sctype == 5 or sctype == 6 or sctype == 7 then
2017-08-04 01:58:31 +02:00
source_list = " main "
-- Furnace: output
elseif sctype == 4 then
2017-08-04 02:19:47 +02:00
source_list = " dst "
-- Unknown source container type. Bail out
else
return false
2017-08-04 01:58:31 +02:00
end
end
2017-08-04 03:34:28 +02:00
-- Automatically select stack slot ID if set to automatic
2017-08-04 02:19:47 +02:00
if not source_stack_id then
source_stack_id = - 1
end
2017-02-14 01:25:02 +01:00
if source_stack_id == - 1 then
2017-08-04 03:00:33 +02:00
local cond = nil
-- Prevent shulker box inception
if dctype == 3 then
cond = is_not_shulker_box
end
2017-08-04 03:34:28 +02:00
source_stack_id = mcl_util.get_eligible_transfer_item_slot ( sinv , source_list , dinv , dpos , cond )
if not source_stack_id then
-- Try again if source is a double container
if sctype == 5 then
spos = mcl_util.get_double_container_neighbor_pos ( spos , snode.param2 , " left " )
smeta = minetest.get_meta ( spos )
sinv = smeta : get_inventory ( )
source_stack_id = mcl_util.get_eligible_transfer_item_slot ( sinv , source_list , dinv , dpos , cond )
if not source_stack_id then
return false
end
else
return false
end
2017-02-14 01:25:02 +01:00
end
end
2017-08-04 01:58:31 +02:00
-- Abort transfer if shulker box wants to go into shulker box
2017-08-03 23:23:33 +02:00
if dctype == 3 then
local stack = sinv : get_stack ( source_list , source_stack_id )
if stack and minetest.get_item_group ( stack : get_name ( ) , " shulker_box " ) == 1 then
return false
end
end
2018-05-12 22:48:49 +02:00
-- Container type 7 does not allow any placement
2018-05-12 21:50:56 +02:00
if dctype == 7 then
2018-05-12 22:48:49 +02:00
return false
2018-05-12 21:50:56 +02:00
end
2017-08-03 23:23:33 +02:00
2017-02-14 00:44:23 +01:00
-- If it's a container, put it into the container
2017-08-03 23:23:33 +02:00
if dctype ~= 0 then
2017-02-21 02:09:34 +01:00
-- Automatically select a destination list if omitted
if not destination_list then
2017-08-03 23:23:33 +02:00
-- Main inventory for most container types
2018-05-12 21:50:56 +02:00
if dctype == 2 or dctype == 3 or dctype == 5 or dctype == 6 or dctype == 7 then
2017-02-21 02:09:34 +01:00
destination_list = " main "
2017-08-03 23:23:33 +02:00
-- Furnace source slot
elseif dctype == 4 then
2017-02-21 02:09:34 +01:00
destination_list = " src "
2017-02-14 00:44:23 +01:00
end
2017-02-21 02:09:34 +01:00
end
if destination_list then
2017-08-03 23:23:33 +02:00
-- Move item
local ok = mcl_util.move_item ( sinv , source_list , source_stack_id , dinv , destination_list )
-- Try transfer to neighbor node if transfer failed and double container
if not ok and dctype == 5 then
2017-08-03 23:27:22 +02:00
dpos = mcl_util.get_double_container_neighbor_pos ( dpos , dnode.param2 , " left " )
2017-08-03 23:23:33 +02:00
dmeta = minetest.get_meta ( dpos )
dinv = dmeta : get_inventory ( )
ok = mcl_util.move_item ( sinv , source_list , source_stack_id , dinv , destination_list )
end
-- Update furnace
if ok and dctype == 4 then
-- Start furnace's timer function, it will sort out whether furnace can burn or not.
minetest.get_node_timer ( dpos ) : start ( 1.0 )
end
return ok
2017-02-14 00:44:23 +01:00
end
end
return false
end
2017-02-14 01:25:02 +01:00
-- Returns the ID of the first non-empty slot in the given inventory list
-- or nil, if inventory is empty.
function mcl_util . get_first_occupied_inventory_slot ( inventory , listname )
2017-08-04 03:00:33 +02:00
return mcl_util.get_eligible_transfer_item_slot ( inventory , listname )
2017-02-14 01:25:02 +01:00
end
2017-02-21 03:00:48 +01:00
-- Returns true if item (itemstring or ItemStack) can be used as a furnace fuel.
-- Returns false otherwise
function mcl_util . is_fuel ( item )
return minetest.get_craft_result ( { method = " fuel " , width = 1 , items = { item } } ) . time ~= 0
end
2017-02-21 16:38:28 +01:00
2017-06-09 20:20:29 +02:00
-- Returns a on_place function for plants
2017-11-15 01:29:17 +01:00
-- * condition: function(pos, node, itemstack)
2017-06-09 20:20:29 +02:00
-- * A function which is called by the on_place function to check if the node can be placed
2017-11-15 01:29:17 +01:00
-- * Must return true, if placement is allowed, false otherwise.
-- * If it returns a string, placement is allowed, but will place this itemstring as a node instead
2017-06-09 20:20:29 +02:00
-- * pos, node: Position and node table of plant node
2017-11-15 01:29:17 +01:00
-- * itemstack: Itemstack to place
2017-06-09 20:20:29 +02:00
function mcl_util . generate_on_place_plant_function ( condition )
return function ( itemstack , placer , pointed_thing )
if pointed_thing.type ~= " node " then
-- no interaction possible with entities
return itemstack
2017-05-14 01:45:57 +02:00
end
2017-06-09 20:20:29 +02:00
-- Call on_rightclick if the pointed node defines it
local node = minetest.get_node ( pointed_thing.under )
if placer and not placer : get_player_control ( ) . sneak then
if minetest.registered_nodes [ node.name ] and minetest.registered_nodes [ node.name ] . on_rightclick then
return minetest.registered_nodes [ node.name ] . on_rightclick ( pointed_thing.under , node , placer , itemstack ) or itemstack
end
end
2017-06-09 19:18:23 +02:00
2017-06-09 20:20:29 +02:00
local place_pos
local def_under = minetest.registered_nodes [ minetest.get_node ( pointed_thing.under ) . name ]
local def_above = minetest.registered_nodes [ minetest.get_node ( pointed_thing.above ) . name ]
2017-06-29 13:02:53 +02:00
if not def_under or not def_above then
return itemstack
end
2017-06-09 20:20:29 +02:00
if def_under.buildable_to then
place_pos = pointed_thing.under
elseif def_above.buildable_to then
place_pos = pointed_thing.above
else
return itemstack
end
2017-06-09 19:18:23 +02:00
2017-06-09 20:20:29 +02:00
-- Check placement rules
2017-11-16 03:13:19 +01:00
local result , param2 = condition ( place_pos , node , itemstack )
if result == true then
local idef = itemstack : get_definition ( )
local new_itemstack , success = minetest.item_place_node ( itemstack , placer , pointed_thing , param2 )
2017-05-14 01:45:57 +02:00
2017-06-09 20:20:29 +02:00
if success then
if idef.sounds and idef.sounds . place then
2020-04-07 00:55:45 +02:00
minetest.sound_play ( idef.sounds . place , { pos = pointed_thing.above , gain = 1 } , true )
2017-06-09 20:20:29 +02:00
end
2017-05-14 01:45:57 +02:00
end
2017-06-09 20:20:29 +02:00
itemstack = new_itemstack
2017-05-14 01:45:57 +02:00
end
2017-06-09 20:20:29 +02:00
return itemstack
end
2017-05-14 01:45:57 +02:00
end
2017-06-09 20:20:29 +02:00
2021-01-26 14:13:21 +01:00
-- adjust the y level of an object to the center of its collisionbox
-- used to get the origin position of entity explosions
function mcl_util . get_object_center ( obj )
local collisionbox = obj : get_properties ( ) . collisionbox
local pos = obj : get_pos ( )
local ymin = collisionbox [ 2 ]
local ymax = collisionbox [ 5 ]
pos.y = pos.y + ( ymax - ymin ) / 2.0
return pos
end
2021-02-10 14:04:49 +01:00
local get_node_emerge_queue = { }
local function ecb_get_far_node ( blockpos , action , calls_remaining , param )
if calls_remaining <= 0 and param then
minetest.log ( " verbose " , " [mcl_util] ecb done for param = " .. param .. " node.name= " .. minetest.get_node ( minetest.string_to_pos ( param ) ) . name )
get_node_emerge_queue [ param ] = nil
end
end
function mcl_util . get_far_node ( pos , force )
local node = minetest.get_node ( pos )
if node.name ~= " ignore " then
return node
end
minetest.get_voxel_manip ( ) : read_from_map ( pos , pos )
node = minetest.get_node ( pos )
if node.name ~= " ignore " or not force then
return node
end
local blockpos = vector.multiply ( vector.floor ( vector.divide ( pos , mcl_vars.MAP_BLOCKSIZE ) ) , mcl_vars.MAP_BLOCKSIZE )
local key = minetest.pos_to_string ( blockpos )
for i = 1 , 2 do -- give engine 2 chances to emerge the data
if not get_node_emerge_queue [ key ] then
get_node_emerge_queue [ key ] = 1
minetest.log ( " verbose " , " [mcl_util] emerge during get_far_node( " .. minetest.pos_to_string ( pos ) .. " ), key= " .. key .. " , blockpos= " .. minetest.pos_to_string ( blockpos ) )
minetest.emerge_area ( blockpos , vector.add ( blockpos , mcl_vars.MAP_BLOCKSIZE - 1 ) , ecb_get_far_node , key )
end
while not get_node_emerge_queue [ key ] do end
minetest.log ( " verbose " , " [mcl_util] emerge finished for node " .. minetest.pos_to_string ( pos ) .. " , key= " .. key .. " , blockpos= " .. minetest.pos_to_string ( blockpos ) .. " , node.name= " .. mcl_util.get_far_node ( pos ) . name )
node = minetest.get_node ( pos )
if node.name ~= " ignore " then
return node
end
minetest.get_voxel_manip ( ) : read_from_map ( pos , pos )
node = minetest.get_node ( pos )
if node.name ~= " ignore " or not force then
return node
end
end
node.name = " air " -- engine continuously returns "ignore" - probably it is a bug
minetest.swap_node ( pos , node ) -- engine continuously returns "ignore" - probably it is a bug
return node -- engine continuously returns "ignore" - probably it is a bug
end