2017-02-11 04:26:00 +01:00
mcl_util = { }
2021-11-09 19:50:49 +01:00
-- Updates all values in t using values from to*.
function table . update ( t , ... )
for _ , to in ipairs { ... } do
for k , v in pairs ( to ) do
t [ k ] = v
end
end
return t
end
-- Updates nil values in t using values from to*.
function table . update_nil ( t , ... )
for _ , to in ipairs { ... } do
for k , v in pairs ( to ) do
if t [ k ] == nil then
t [ k ] = v
end
end
end
return t
end
2022-11-08 23:27:10 +01:00
local LOGGING_ON = minetest.settings : get_bool ( " mcl_logging_default " , false )
local LOG_MODULE = " [MCL2] "
2022-11-08 23:54:16 +01:00
function mcl_util . mcl_log ( message , module , bypass_default_logger )
2022-11-08 23:27:10 +01:00
local selected_module = LOG_MODULE
if module then
selected_module = module
end
2022-11-08 23:54:16 +01:00
if ( bypass_default_logger or LOGGING_ON ) and message then
2022-11-08 23:27:10 +01:00
minetest.log ( selected_module .. " " .. message )
end
end
2022-10-06 07:44:59 +02:00
function mcl_util . file_exists ( name )
2022-10-28 13:34:58 +02:00
if type ( name ) ~= " string " then return end
2022-10-06 07:44:59 +02:00
local f = io.open ( name )
if not f then
return false
end
f : close ( )
return true
end
2017-02-11 04:26:00 +01:00
-- 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
2021-05-25 12:52:25 +02:00
local function is_not_shulker_box ( itemstack )
2017-08-04 03:00:33 +02:00
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
2021-05-25 12:52:25 +02:00
local function normalize_double_container ( pos , node , ctype )
2017-08-04 03:34:28 +02:00
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
2022-02-22 18:03:27 +01:00
local function drop_item_stack ( pos , stack )
if not stack or stack : is_empty ( ) then return end
local drop_offset = vector.new ( math.random ( ) - 0.5 , 0 , math.random ( ) - 0.5 )
minetest.add_item ( vector.add ( pos , drop_offset ) , stack )
end
2022-02-07 08:47:41 +01:00
function mcl_util . drop_items_from_meta_container ( listname )
2022-01-20 09:22:17 +01:00
return function ( pos , oldnode , oldmetadata )
2022-02-22 18:03:27 +01:00
if oldmetadata and oldmetadata.inventory then
-- process in after_dig_node callback
local main = oldmetadata.inventory . main
if not main then return end
for _ , stack in pairs ( main ) do
drop_item_stack ( pos , stack )
end
else
local meta = minetest.get_meta ( pos )
local inv = meta : get_inventory ( )
for i = 1 , inv : get_size ( " main " ) do
drop_item_stack ( pos , inv : get_stack ( " main " , i ) )
2022-01-20 09:22:17 +01:00
end
2022-02-22 18:03:27 +01:00
meta : from_table ( )
2022-01-20 09:22:17 +01:00
end
end
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-03-05 09:37:27 +01:00
function mcl_util . get_color ( colorstr )
local mc_color = mcl_colors [ colorstr : upper ( ) ]
if mc_color then
2021-03-05 10:20:19 +01:00
colorstr = mc_color
2021-04-06 14:50:34 +02:00
elseif # colorstr ~= 7 or colorstr : sub ( 1 , 1 ) ~= " # " then
2021-03-05 09:37:27 +01:00
return
end
local hex = tonumber ( colorstr : sub ( 2 , 7 ) , 16 )
if hex then
return colorstr , hex
end
end
2021-04-14 15:46:52 +02:00
function mcl_util . call_on_rightclick ( itemstack , player , pointed_thing )
-- Call on_rightclick if the pointed node defines it
if pointed_thing and pointed_thing.type == " node " then
2021-04-18 16:03:23 +02:00
local pos = pointed_thing.under
local node = minetest.get_node ( pos )
2021-04-14 15:46:52 +02:00
if player and not player : get_player_control ( ) . sneak then
2021-04-18 16:03:23 +02:00
local nodedef = minetest.registered_nodes [ node.name ]
local on_rightclick = nodedef and nodedef.on_rightclick
if on_rightclick then
return on_rightclick ( pos , node , player , itemstack , pointed_thing ) or itemstack
2021-04-14 15:46:52 +02:00
end
end
end
end
function mcl_util . calculate_durability ( itemstack )
local unbreaking_level = mcl_enchanting.get_enchantment ( itemstack , " unbreaking " )
local armor_uses = minetest.get_item_group ( itemstack : get_name ( ) , " mcl_armor_uses " )
local uses
if armor_uses > 0 then
uses = armor_uses
if unbreaking_level > 0 then
uses = uses / ( 0.6 + 0.4 / ( unbreaking_level + 1 ) )
end
else
local def = itemstack : get_definition ( )
if def then
local fixed_uses = def._mcl_uses
if fixed_uses then
uses = fixed_uses
if unbreaking_level > 0 then
uses = uses * ( unbreaking_level + 1 )
end
end
end
2021-12-02 17:28:19 +01:00
local _ , groupcap = next ( itemstack : get_tool_capabilities ( ) . groupcaps )
uses = uses or ( groupcap or { } ) . uses
2021-04-14 15:46:52 +02:00
end
return uses or 0
end
function mcl_util . use_item_durability ( itemstack , n )
local uses = mcl_util.calculate_durability ( itemstack )
itemstack : add_wear ( 65535 / uses * n )
end
function mcl_util . deal_damage ( target , damage , mcl_reason )
local luaentity = target : get_luaentity ( )
if luaentity then
if luaentity.deal_damage then
2021-04-25 13:50:07 +02:00
luaentity : deal_damage ( damage , mcl_reason or { type = " generic " } )
2021-04-14 15:46:52 +02:00
return
2022-05-25 14:02:10 +02:00
elseif luaentity.is_mob then
2021-04-25 13:50:07 +02:00
-- local puncher = mcl_reason and mcl_reason.direct or target
-- target:punch(puncher, 1.0, {full_punch_interval = 1.0, damage_groups = {fleshy = damage}}, vector.direction(puncher:get_pos(), target:get_pos()), damage)
2021-05-05 08:54:03 +02:00
if luaentity.health > 0 then
luaentity.health = luaentity.health - damage
end
2021-04-14 15:46:52 +02:00
return
end
end
2021-05-05 08:54:03 +02:00
local hp = target : get_hp ( )
if hp > 0 then
target : set_hp ( hp - damage , { _mcl_reason = mcl_reason } )
end
2021-04-14 15:46:52 +02:00
end
2021-04-18 20:21:11 +02:00
function mcl_util . get_hp ( obj )
local luaentity = obj : get_luaentity ( )
2022-05-25 14:02:10 +02:00
if luaentity and luaentity.is_mob then
2021-04-18 20:21:11 +02:00
return luaentity.health
else
return obj : get_hp ( )
end
end
2021-04-14 15:46:52 +02:00
function mcl_util . get_inventory ( object , create )
if object : is_player ( ) then
return object : get_inventory ( )
else
local luaentity = object : get_luaentity ( )
local inventory = luaentity.inventory
if create and not inventory and luaentity.create_inventory then
inventory = luaentity : create_inventory ( )
end
return inventory
end
end
2021-04-25 16:42:38 +02:00
function mcl_util . get_wielded_item ( object )
if object : is_player ( ) then
return object : get_wielded_item ( )
else
-- ToDo: implement getting wielditems from mobs as soon as mobs have wielditems
return ItemStack ( )
end
end
function mcl_util . get_object_name ( object )
if object : is_player ( ) then
return object : get_player_name ( )
else
local luaentity = object : get_luaentity ( )
2021-04-29 11:17:52 +02:00
if not luaentity then
2021-05-05 13:24:23 +02:00
return tostring ( object )
2021-04-29 11:17:52 +02:00
end
2021-04-25 16:42:38 +02:00
return luaentity.nametag and luaentity.nametag ~= " " and luaentity.nametag or luaentity.description or luaentity.name
end
end
2021-09-09 11:54:58 +02:00
function mcl_util . replace_mob ( obj , mob )
2022-11-07 13:06:34 +01:00
if not obj then return end
2021-09-09 11:54:58 +02:00
local rot = obj : get_yaw ( )
local pos = obj : get_pos ( )
obj : remove ( )
obj = minetest.add_entity ( pos , mob )
2022-11-07 13:06:34 +01:00
if not obj then return end
2021-09-09 11:54:58 +02:00
obj : set_yaw ( rot )
return obj
end
2022-02-17 15:35:51 +01:00
2022-02-17 16:30:41 +01:00
function mcl_util . get_pointed_thing ( player , liquid )
2022-02-17 15:35:51 +01:00
local pos = vector.offset ( player : get_pos ( ) , 0 , player : get_properties ( ) . eye_height , 0 )
local look_dir = vector.multiply ( player : get_look_dir ( ) , 5 )
local pos2 = vector.add ( pos , look_dir )
2022-02-17 16:30:41 +01:00
local ray = minetest.raycast ( pos , pos2 , false , liquid )
2022-05-26 07:29:28 +02:00
2022-02-17 15:35:51 +01:00
if ray then
for pointed_thing in ray do
return pointed_thing
end
end
end
2022-10-06 04:01:56 +02:00
-- This following part is 2 wrapper functions + helpers for
-- object:set_bones
-- and player:set_properties preventing them from being resent on
-- every globalstep when they have not changed.
local function roundN ( n , d )
if type ( n ) ~= " number " then return n end
local m = 10 ^ d
return math.floor ( n * m + 0.5 ) / m
end
local function close_enough ( a , b )
local rt = true
if type ( a ) == " table " and type ( b ) == " table " then
for k , v in pairs ( a ) do
if roundN ( v , 2 ) ~= roundN ( b [ k ] , 2 ) then
rt = false
break
end
end
else
rt = roundN ( a , 2 ) == roundN ( b , 2 )
end
return rt
end
local function props_changed ( props , oldprops )
local changed = false
local p = { }
for k , v in pairs ( props ) do
if not close_enough ( v , oldprops [ k ] ) then
p [ k ] = v
changed = true
end
end
return changed , p
end
--tests for roundN
local test_round1 = 15
local test_round2 = 15.00199999999
local test_round3 = 15.00111111
local test_round4 = 15.00999999
assert ( roundN ( test_round1 , 2 ) == roundN ( test_round1 , 2 ) )
assert ( roundN ( test_round1 , 2 ) == roundN ( test_round2 , 2 ) )
assert ( roundN ( test_round1 , 2 ) == roundN ( test_round3 , 2 ) )
assert ( roundN ( test_round1 , 2 ) ~= roundN ( test_round4 , 2 ) )
-- tests for close_enough
local test_cb = { - 0.35 , 0 , - 0.35 , 0.35 , 0.8 , 0.35 } --collisionboxes
local test_cb_close = { - 0.351213 , 0 , - 0.35 , 0.35 , 0.8 , 0.351212 }
local test_cb_diff = { - 0.35 , 0 , - 1.35 , 0.35 , 0.8 , 0.35 }
local test_eh = 1.65 --eye height
local test_eh_close = 1.65123123
local test_eh_diff = 1.35
local test_nt = { r = 225 , b = 225 , a = 225 , g = 225 } --nametag
local test_nt_diff = { r = 225 , b = 225 , a = 0 , g = 225 }
assert ( close_enough ( test_cb , test_cb_close ) )
assert ( not close_enough ( test_cb , test_cb_diff ) )
assert ( close_enough ( test_eh , test_eh_close ) )
assert ( not close_enough ( test_eh , test_eh_diff ) )
assert ( not close_enough ( test_nt , test_nt_diff ) ) --no floats involved here
--tests for properties_changed
local test_properties_set1 = { collisionbox = { - 0.35 , 0 , - 0.35 , 0.35 , 0.8 , 0.35 } , eye_height = 0.65 , nametag_color = { r = 225 , b = 225 , a = 225 , g = 225 } }
local test_properties_set2 = { collisionbox = { - 0.35 , 0 , - 0.35 , 0.35 , 0.8 , 0.35 } , eye_height = 1.35 , nametag_color = { r = 225 , b = 225 , a = 225 , g = 225 } }
local test_p1 , _ = props_changed ( test_properties_set1 , test_properties_set1 )
local test_p2 , _ = props_changed ( test_properties_set1 , test_properties_set2 )
assert ( not test_p1 )
assert ( test_p2 )
function mcl_util . set_properties ( obj , props )
local changed , p = props_changed ( props , obj : get_properties ( ) )
if changed then
obj : set_properties ( p )
end
end
2022-11-06 17:48:22 +01:00
function mcl_util . set_bone_position ( obj , bone , pos , rot )
local current_pos , current_rot = obj : get_bone_position ( bone )
local pos_equal = not pos or vector.equals ( vector.round ( current_pos ) , vector.round ( pos ) )
local rot_equal = not rot or vector.equals ( vector.round ( current_rot ) , vector.round ( rot ) )
if not pos_equal or not rot_equal then
obj : set_bone_position ( bone , pos or current_pos , rot or current_rot )
2022-10-06 04:01:56 +02:00
end
end
2023-01-06 21:07:07 +01:00
--[[Check for a protection violation on given nodes.
--
-- @param affected_nodes Node to check for protection violation.
-- @param player The player performing the action.
-- @param create_log Default: False. Should a log message be created on violation detection.
--
-- @return true on protection violation detection. false otherwise.
] ]
function mcl_util . check_nodes_protection ( affected_nodes , player , create_log )
create_log = create_log or false
local name = player and player : get_player_name ( ) or " "
for _ , position in ipairs ( affected_nodes ) do
if minetest.is_protected ( position , name ) then
if create_log then
minetest.log ( " action " , name .. " tried violating protection at position "
.. minetest.pos_to_string ( position ) )
end
minetest.record_protection_violation ( position , name )
return true
end
end
return false
end
--[[Check protection violation for a planting action.
--
-- @param pointed_thing The pointed_thing table for the object the action was pointed at.
-- @param player See mcl_util.check_nodes_protection.
-- @param create_log See mcl_util.check_nodes_protection.
--
-- @return See mcl_util.check_nodes_protection.
] ]
function mcl_util . check_planting_protection ( pointed_thing , player , create_log )
-- We dont want to allow planting on top of protected nodes, even if the air is unprotected.
return mcl_util.check_nodes_protection ( { pointed_thing.above , pointed_thing.under } , player ,
create_log )
end
--[[Check protection violation for a node placement action.
--
-- @param pointed_thing The pointed_thing table for the object the action was pointed at.
-- @param player See mcl_util.check_nodes_protection.
-- @param create_log See mcl_util.check_nodes_protection.
--
-- @return See mcl_util.check_nodes_protection.
] ]
function mcl_util . check_placement_protection ( pointed_thing , player , create_log )
return mcl_util.check_nodes_protection ( { pointed_thing.above } , player , create_log )
end
--[[Check protection violation for a node modification action.
--
-- Such as using a shovel or a hoe on dirt, destroying a block or using a chest.
--
-- @param pointed_thing The pointed_thing table for the object the action was pointed at.
-- @param player See mcl_util.check_nodes_protection.
-- @param create_log See mcl_util.check_nodes_protection.
--
-- @return See mcl_util.check_nodes_protection.
] ]
function mcl_util . check_node_modification_protection ( pointed_thing , player , create_log )
return mcl_util.check_nodes_protection ( { pointed_thing.under } , player , create_log )
end