2017-02-11 05:26:00 +02:00
mcl_util = { }
2021-11-09 20:50:49 +02: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
2017-02-11 05:26:00 +02: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 05:33:06 +02:00
function mcl_util . rotate_axis_and_place ( itemstack , placer , pointed_thing , infinitestacks , invert_wall )
2017-02-11 05:26:00 +02: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 05:33:06 +02: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 17:08:40 +03:00
minetest.is_creative_enabled ( placer : get_player_name ( ) ) ,
2017-02-11 05:33:06 +02:00
placer : get_player_control ( ) . sneak )
return itemstack
end
2017-08-04 00:27:22 +03: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 04:00:33 +03: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 22:50:56 +03:00
-- Returns true if itemstack is a shulker box
2021-05-25 13:52:25 +03:00
local function is_not_shulker_box ( itemstack )
2017-08-04 04:00:33 +03:00
local g = minetest.get_item_group ( itemstack : get_name ( ) , " shulker_box " )
return g == 0 or g == nil
end
2017-08-04 00:23:33 +03:00
-- Moves a single item from one inventory to another.
2017-02-14 01:44:23 +02: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 02:25:02 +02: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 01:44:23 +02: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 01:10:37 +02:00
-- Returns true on success and false on failure
2017-02-14 01:44:23 +02:00
-- Possible failures: No item in source slot, destination inventory full
2017-02-14 01:10:37 +02:00
function mcl_util . move_item ( source_inventory , source_list , source_stack_id , destination_inventory , destination_list )
2017-02-14 02:25:02 +02: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 01:10:37 +02: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 16:30:32 +03: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 01:10:37 +02:00
return false
end
stack : take_item ( )
source_inventory : set_stack ( source_list , source_stack_id , stack )
2019-04-05 16:30:32 +03:00
destination_inventory : add_item ( destination_list , new_stack )
2017-02-14 01:10:37 +02:00
return true
end
end
return false
end
2017-02-14 01:44:23 +02:00
2017-08-04 00:23:33 +03: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 01:44:23 +02: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 03:19:47 +03: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 04:00:33 +03: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 03:19:47 +03: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-04 00:23:33 +03:00
local dpos = table.copy ( destination_pos )
2017-08-04 04:34:28 +03:00
local spos = table.copy ( source_pos )
local snode = minetest.get_node ( spos )
local dnode = minetest.get_node ( dpos )
2017-08-04 00:23:33 +03:00
local dctype = minetest.get_item_group ( dnode.name , " container " )
2017-08-04 03:19:47 +03:00
local sctype = minetest.get_item_group ( snode.name , " container " )
2017-08-04 00:23:33 +03:00
2018-05-12 23:48:49 +03:00
-- Container type 7 does not allow any movement
if sctype == 7 then
return false
end
2017-08-04 00:23:33 +03:00
-- Normalize double container by forcing to always use the left segment first
2021-05-25 13:52:25 +03:00
local function normalize_double_container ( pos , node , ctype )
2017-08-04 04:34:28 +03: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-04 00:23:33 +03:00
end
2017-08-04 04:34:28 +03:00
return pos , node , ctype
2017-08-04 00:23:33 +03:00
end
2017-08-04 04:34:28 +03: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-04 00:23:33 +03:00
local dmeta = minetest.get_meta ( dpos )
2017-02-14 01:44:23 +02:00
local sinv = smeta : get_inventory ( )
local dinv = dmeta : get_inventory ( )
2017-08-04 02:58:31 +03:00
-- Default source lists
if not source_list then
-- Main inventory for most container types
2018-05-12 22:50:56 +03:00
if sctype == 2 or sctype == 3 or sctype == 5 or sctype == 6 or sctype == 7 then
2017-08-04 02:58:31 +03:00
source_list = " main "
-- Furnace: output
elseif sctype == 4 then
2017-08-04 03:19:47 +03:00
source_list = " dst "
-- Unknown source container type. Bail out
else
return false
2017-08-04 02:58:31 +03:00
end
end
2017-08-04 04:34:28 +03:00
-- Automatically select stack slot ID if set to automatic
2017-08-04 03:19:47 +03:00
if not source_stack_id then
source_stack_id = - 1
end
2017-02-14 02:25:02 +02:00
if source_stack_id == - 1 then
2017-08-04 04:00:33 +03:00
local cond = nil
-- Prevent shulker box inception
if dctype == 3 then
cond = is_not_shulker_box
end
2017-08-04 04:34:28 +03: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 02:25:02 +02:00
end
end
2017-08-04 02:58:31 +03:00
-- Abort transfer if shulker box wants to go into shulker box
2017-08-04 00:23:33 +03: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 23:48:49 +03:00
-- Container type 7 does not allow any placement
2018-05-12 22:50:56 +03:00
if dctype == 7 then
2018-05-12 23:48:49 +03:00
return false
2018-05-12 22:50:56 +03:00
end
2017-08-04 00:23:33 +03:00
2017-02-14 01:44:23 +02:00
-- If it's a container, put it into the container
2017-08-04 00:23:33 +03:00
if dctype ~= 0 then
2017-02-21 03:09:34 +02:00
-- Automatically select a destination list if omitted
if not destination_list then
2017-08-04 00:23:33 +03:00
-- Main inventory for most container types
2018-05-12 22:50:56 +03:00
if dctype == 2 or dctype == 3 or dctype == 5 or dctype == 6 or dctype == 7 then
2017-02-21 03:09:34 +02:00
destination_list = " main "
2017-08-04 00:23:33 +03:00
-- Furnace source slot
elseif dctype == 4 then
2017-02-21 03:09:34 +02:00
destination_list = " src "
2017-02-14 01:44:23 +02:00
end
2017-02-21 03:09:34 +02:00
end
if destination_list then
2017-08-04 00:23:33 +03: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-04 00:27:22 +03:00
dpos = mcl_util.get_double_container_neighbor_pos ( dpos , dnode.param2 , " left " )
2017-08-04 00:23:33 +03: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 01:44:23 +02:00
end
end
return false
end
2017-02-14 02:25:02 +02: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 04:00:33 +03:00
return mcl_util.get_eligible_transfer_item_slot ( inventory , listname )
2017-02-14 02:25:02 +02:00
end
2017-02-21 04:00:48 +02: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 17:38:28 +02:00
2017-06-09 21:20:29 +03:00
-- Returns a on_place function for plants
2017-11-15 02:29:17 +02:00
-- * condition: function(pos, node, itemstack)
2017-06-09 21:20:29 +03:00
-- * A function which is called by the on_place function to check if the node can be placed
2017-11-15 02:29:17 +02: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 21:20:29 +03:00
-- * pos, node: Position and node table of plant node
2017-11-15 02:29:17 +02:00
-- * itemstack: Itemstack to place
2017-06-09 21:20:29 +03: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 02:45:57 +03:00
end
2017-06-09 21:20:29 +03: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 20:18:23 +03:00
2017-06-09 21:20:29 +03: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 14:02:53 +03:00
if not def_under or not def_above then
return itemstack
end
2017-06-09 21:20:29 +03: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 20:18:23 +03:00
2017-06-09 21:20:29 +03:00
-- Check placement rules
2017-11-16 04:13:19 +02: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 02:45:57 +03:00
2017-06-09 21:20:29 +03:00
if success then
if idef.sounds and idef.sounds . place then
2020-04-07 01:55:45 +03:00
minetest.sound_play ( idef.sounds . place , { pos = pointed_thing.above , gain = 1 } , true )
2017-06-09 21:20:29 +03:00
end
2017-05-14 02:45:57 +03:00
end
2017-06-09 21:20:29 +03:00
itemstack = new_itemstack
2017-05-14 02:45:57 +03:00
end
2017-06-09 21:20:29 +03:00
return itemstack
end
2017-05-14 02:45:57 +03:00
end
2017-06-09 21:20:29 +03:00
2021-01-26 15:13:21 +02: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 10:37:27 +02:00
function mcl_util . get_color ( colorstr )
local mc_color = mcl_colors [ colorstr : upper ( ) ]
if mc_color then
2021-03-05 11:20:19 +02:00
colorstr = mc_color
2021-04-06 15:50:34 +03:00
elseif # colorstr ~= 7 or colorstr : sub ( 1 , 1 ) ~= " # " then
2021-03-05 10:37:27 +02:00
return
end
local hex = tonumber ( colorstr : sub ( 2 , 7 ) , 16 )
if hex then
return colorstr , hex
end
end
2021-04-14 16:46:52 +03: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 17:03:23 +03:00
local pos = pointed_thing.under
local node = minetest.get_node ( pos )
2021-04-14 16:46:52 +03:00
if player and not player : get_player_control ( ) . sneak then
2021-04-18 17:03:23 +03: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 16:46:52 +03: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 18:28:19 +02:00
local _ , groupcap = next ( itemstack : get_tool_capabilities ( ) . groupcaps )
uses = uses or ( groupcap or { } ) . uses
2021-04-14 16:46:52 +03: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 14:50:07 +03:00
luaentity : deal_damage ( damage , mcl_reason or { type = " generic " } )
2021-04-14 16:46:52 +03:00
return
elseif luaentity._cmi_is_mob then
2021-04-25 14:50:07 +03: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 09:54:03 +03:00
if luaentity.health > 0 then
luaentity.health = luaentity.health - damage
end
2021-04-14 16:46:52 +03:00
return
end
end
2021-05-05 09:54:03 +03:00
local hp = target : get_hp ( )
if hp > 0 then
target : set_hp ( hp - damage , { _mcl_reason = mcl_reason } )
end
2021-04-14 16:46:52 +03:00
end
2021-04-18 21:21:11 +03:00
function mcl_util . get_hp ( obj )
local luaentity = obj : get_luaentity ( )
if luaentity and luaentity._cmi_is_mob then
return luaentity.health
else
return obj : get_hp ( )
end
end
2021-04-14 16:46:52 +03: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 17:42:38 +03: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 12:17:52 +03:00
if not luaentity then
2021-05-05 14:24:23 +03:00
return tostring ( object )
2021-04-29 12:17:52 +03:00
end
2021-04-25 17:42:38 +03:00
return luaentity.nametag and luaentity.nametag ~= " " and luaentity.nametag or luaentity.description or luaentity.name
end
end
2021-09-09 12:54:58 +03:00
function mcl_util . replace_mob ( obj , mob )
local rot = obj : get_yaw ( )
local pos = obj : get_pos ( )
obj : remove ( )
obj = minetest.add_entity ( pos , mob )
obj : set_yaw ( rot )
return obj
end