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-01-02 23:22:56 +01:00
-- Creates a function that calls to the minetest
-- function minetest_rotate_and_place. It rotates
-- a block based on where it thinks the player is facing
-- at the moment. This is typically called by pillar-like nodes.
2022-01-02 23:32:43 +01:00
2017-02-11 04:33:06 +01:00
function mcl_util . rotate_axis ( itemstack , placer , pointed_thing )
2022-01-02 23:22:56 +01:00
minetest.rotate_and_place ( itemstack , placer , pointed_thing ,
minetest.is_creative_enabled ( placer : get_player_name ( ) ) )
2017-02-11 04:33:06 +01:00
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
-- 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
elseif luaentity._cmi_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 ( )
if luaentity and luaentity._cmi_is_mob then
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 )
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
2022-01-24 17:26:14 +01:00
function mcl_util . get_pointed_thing ( player )
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 )
local ray = minetest.raycast ( pos , pos2 , false , true )
if ray then
for pointed_thing in ray do
return pointed_thing
end
end
end
2022-02-14 03:23:27 +01:00
local possible_hackers = { }
function mcl_util . is_player ( obj )
if not obj then return end
2022-02-14 03:39:47 +01:00
if not obj.is_player then return end
2022-02-14 03:23:27 +01:00
if not obj : is_player ( ) then return end
local name = obj : get_player_name ( )
if not name then return end
if possible_hackers [ name ] then return end
return true
end
minetest.register_on_authplayer ( function ( name , ip , is_success )
if not is_success then return end
possible_hackers [ name ] = true
end )
minetest.register_on_joinplayer ( function ( player )
possible_hackers [ player : get_player_name ( ) ] = nil
end )