2 local item_drop_settings
= {} --settings table
3 item_drop_settings
.age
= 1.0 --how old a dropped item (_insta_collect==false) has to be before collecting
4 item_drop_settings
.radius_magnet
= 2.0 --radius of item magnet. MUST BE LARGER THAN radius_collect!
5 item_drop_settings
.radius_collect
= 0.2 --radius of collection
6 item_drop_settings
.player_collect_height
= 1.0 --added to their pos y value
7 item_drop_settings
.collection_safety
= false --do this to prevent items from flying away on laggy servers
8 item_drop_settings
.random_item_velocity
= true --this sets random item velocity if velocity is 0
9 item_drop_settings
.drop_single_item
= false --if true, the drop control drops 1 item instead of the entire stack, and sneak+drop drops the stack
10 -- drop_single_item is disabled by default because it is annoying to throw away items from the intentory screen
12 item_drop_settings
.magnet_time
= 0.75 -- how many seconds an item follows the player before giving up
14 local get_gravity
= function()
15 return tonumber(minetest
.settings
:get("movement_gravity")) or 9.81
18 local check_pickup_achievements
= function(object
, player
)
19 local itemname
= ItemStack(object
:get_luaentity().itemstring
):get_name()
20 if minetest
.get_item_group(itemname
, "tree") ~= 0 then
21 awards
.unlock(player
:get_player_name(), "mcl:mineWood")
22 elseif itemname
== "mcl_mobitems:blaze_rod" then
23 awards
.unlock(player
:get_player_name(), "mcl:blazeRod")
24 elseif itemname
== "mcl_mobitems:leather" then
25 awards
.unlock(player
:get_player_name(), "mcl:killCow")
26 elseif itemname
== "mcl_core:diamond" then
27 awards
.unlock(player
:get_player_name(), "mcl:diamonds")
31 local enable_physics
= function(object
, luaentity
, ignore_check
)
32 if luaentity
.physical_state
== false or ignore_check
== true then
33 luaentity
.physical_state
= true
34 object
:set_properties({
37 object
:set_velocity({x
=0,y
=0,z
=0})
38 object
:set_acceleration({x
=0,y
=-get_gravity(),z
=0})
42 local disable_physics
= function(object
, luaentity
, ignore_check
, reset_movement
)
43 if luaentity
.physical_state
== true or ignore_check
== true then
44 luaentity
.physical_state
= false
45 object
:set_properties({
48 if reset_movement
~= false then
49 object
:set_velocity({x
=0,y
=0,z
=0})
50 object
:set_acceleration({x
=0,y
=0,z
=0})
55 minetest
.register_globalstep(function(dtime
)
56 for _
,player
in ipairs(minetest
.get_connected_players()) do
57 if player
:get_hp() > 0 or not minetest
.settings
:get_bool("enable_damage") then
58 local pos
= player
:get_pos()
59 local inv
= player
:get_inventory()
60 local checkpos
= {x
=pos
.x
,y
=pos
.y
+ item_drop_settings
.player_collect_height
,z
=pos
.z
}
62 --magnet and collection
63 for _
,object
in ipairs(minetest
.get_objects_inside_radius(checkpos
, item_drop_settings
.radius_magnet
)) do
64 if not object
:is_player() and object
:get_luaentity() and object
:get_luaentity().name
== "__builtin:item" and object
:get_luaentity()._magnet_timer
and (object
:get_luaentity()._insta_collect
or (object
:get_luaentity().age
> item_drop_settings
.age
)) then
65 object
:get_luaentity()._magnet_timer
= object
:get_luaentity()._magnet_timer
+ dtime
66 local collected
= false
67 if object
:get_luaentity()._magnet_timer
>= 0 and object
:get_luaentity()._magnet_timer
< item_drop_settings
.magnet_time
and inv
and inv
:room_for_item("main", ItemStack(object
:get_luaentity().itemstring
)) then
70 if vector
.distance(checkpos
, object
:get_pos()) <= item_drop_settings
.radius_collect
and not object
:get_luaentity()._removed
then
71 -- Ignore if itemstring is not set yet
72 if object
:get_luaentity().itemstring
~= "" then
73 inv
:add_item("main", ItemStack(object
:get_luaentity().itemstring
))
74 minetest
.sound_play("item_drop_pickup", {
76 max_hear_distance
= 16,
79 check_pickup_achievements(object
, player
)
83 -- This just prevents this section to be run again because object:remove() doesn't remove the item immediately.
84 object
:get_luaentity()._removed
= true
92 object
:get_luaentity()._magnet_active
= true
93 object
:get_luaentity()._collector_timer
= 0
95 -- Move object to player
96 disable_physics(object
, object
:get_luaentity())
98 local opos
= object
:get_pos()
99 local vec
= vector
.subtract(checkpos
, opos
)
100 vec
= vector
.add(opos
, vector
.divide(vec
, 2))
104 --fix eternally falling items
105 minetest
.after(0, function(object
)
106 local lua
= object
:get_luaentity()
108 object
:setacceleration({x
=0, y
=0, z
=0})
113 --this is a safety to prevent items flying away on laggy servers
114 if item_drop_settings
.collection_safety
== true then
115 if object
:get_luaentity().init
~= true then
116 object
:get_luaentity().init
= true
117 minetest
.after(1, function(args
)
118 local playername
= args
[1]
119 local player
= minetest
.get_player_by_name(playername
)
120 local object
= args
[2]
121 local lua
= object
:get_luaentity()
122 if player
== nil or not player
:is_player() or object
== nil or lua
== nil or lua
.itemstring
== nil then
125 if inv
:room_for_item("main", ItemStack(object
:get_luaentity().itemstring
)) then
126 inv
:add_item("main", ItemStack(object
:get_luaentity().itemstring
))
127 if not object
:get_luaentity()._removed
then
128 minetest
.sound_play("item_drop_pickup", {
130 max_hear_distance
= 16,
134 check_pickup_achievements(object
, player
)
135 object
:get_luaentity()._removed
= true
138 enable_physics(object
, object
:get_luaentity())
140 end, {player
:get_player_name(), object
})
146 if not collected
then
147 if object
:get_luaentity()._magnet_timer
> 1 then
148 object
:get_luaentity()._magnet_timer
= -item_drop_settings
.magnet_time
149 object
:get_luaentity()._magnet_active
= false
150 elseif object
:get_luaentity()._magnet_timer
< 0 then
151 object
:get_luaentity()._magnet_timer
= object
:get_luaentity()._magnet_timer
+ dtime
162 local minigroups
= { "shearsy", "swordy", "shearsy_wool", "swordy_cobweb" }
163 local basegroups
= { "pickaxey", "axey", "shovely" }
164 local materials
= { "wood", "gold", "stone", "iron", "diamond" }
166 -- Checks if the given node would drop its useful drop if dug by a tool
167 -- with the given tool capabilities. Returns true if it will yield its useful
168 -- drop, false otherwise.
169 local check_can_drop
= function(node_name
, tool_capabilities
)
170 local handy
= minetest
.get_item_group(node_name
, "handy")
171 local dig_immediate
= minetest
.get_item_group(node_name
, "dig_immediate")
172 if handy
== 1 or dig_immediate
== 2 or dig_immediate
== 3 then
176 if tool_capabilities
then
177 toolgroupcaps
= tool_capabilities
.groupcaps
182 -- Compare node groups with tool capabilities
183 for m
=1, #minigroups
do
184 local minigroup
= minigroups
[m
]
185 local g
= minetest
.get_item_group(node_name
, minigroup
)
187 local plus
= minigroup
.. "_dig"
188 if toolgroupcaps
[plus
] then
193 for b
=1, #basegroups
do
194 local basegroup
= basegroups
[b
]
195 local g
= minetest
.get_item_group(node_name
, basegroup
)
197 for m
=g
, #materials
do
198 local plus
= basegroup
.. "_dig_"..materials
[m
]
199 if toolgroupcaps
[plus
] then
210 function minetest
.handle_node_drops(pos
, drops
, digger
)
211 -- NOTE: This function override allows digger to be nil.
212 -- This means there is no digger. This is a special case which allows this function to be called
213 -- by hand. Creative Mode is intentionally ignored in this case.
215 local doTileDrops
= minetest
.settings
:get_bool("mcl_doTileDrops") or true
216 if (digger
~= nil and minetest
.settings
:get_bool("creative_mode")) or doTileDrops
== false then
220 -- Check if node will yield its useful drop by the digger's tool
221 local dug_node
= minetest
.get_node(pos
)
223 if digger
~= nil then
224 local tool
= digger
:get_wielded_item()
225 toolcaps
= tool
:get_tool_capabilities()
227 if not check_can_drop(dug_node
.name
, toolcaps
) then
232 --[[ Special node drops when dug by shears by reading _mcl_shears_drop
233 from the node definition.
234 Definition of _mcl_shears_drop:
235 * true: Drop itself when dug by shears
236 * table: Drop every itemstring in this table when dub by shears
238 local nodedef
= minetest
.registered_nodes
[dug_node
.name
]
239 if toolcaps
~= nil and toolcaps
.groupcaps
and toolcaps
.groupcaps
.shearsy_dig
and nodedef
._mcl_shears_drop
then
240 if nodedef
._mcl_shears_drop
== true then
241 drops
= { dug_node
.name
}
243 drops
= nodedef
._mcl_shears_drop
247 for _
,item
in ipairs(drops
) do
249 if type(item
) == "string" then
253 count
= item
:get_count()
254 name
= item
:get_name()
257 local obj
= core
.add_item(pos
, name
)
259 local x
= math
.random(1, 5)
260 if math
.random(1,2) == 1 then
263 local z
= math
.random(1, 5)
264 if math
.random(1,2) == 1 then
267 obj
:setvelocity({x
=1/x
, y
=obj
:getvelocity().y
, z
=1/z
})
273 -- Drop single items by default
274 function minetest
.item_drop(itemstack
, dropper
, pos
)
275 if dropper
and dropper
:is_player() then
276 local v
= dropper
:get_look_dir()
277 local p
= {x
=pos
.x
, y
=pos
.y
+1.2, z
=pos
.z
}
278 local cs
= itemstack
:get_count()
279 if dropper
:get_player_control().sneak
then
282 local item
= itemstack
:take_item(cs
)
283 local obj
= core
.add_item(p
, item
)
289 -- Force collection delay
290 obj
:get_luaentity()._insta_collect
= false
296 --modify builtin:item
298 local time_to_live
= tonumber(minetest
.settings
:get("item_entity_ttl"))
299 if not time_to_live
then
303 core
.register_entity(":__builtin:item", {
304 initial_properties
= {
307 collide_with_objects
= false,
308 collisionbox
= {-0.3, -0.3, -0.3, 0.3, 0.3, 0.3},
309 visual
= "wielditem",
310 visual_size
= {x
= 0.4, y
= 0.4},
312 spritediv
= {x
= 1, y
= 1},
313 initial_sprite_basepos
= {x
= 0, y
= 0},
318 -- Itemstring of dropped item. The empty string is used when the item is not yet initialized yet.
319 -- The itemstring MUST be set immediately to a non-empty string after creating the entity.
320 -- The hand is NOT permitted as dropped item. ;-)
321 -- Item entities will be deleted if they still have an empty itemstring on their first on_step tick.
324 -- If true, item will fall
325 physical_state
= true,
327 -- If item entity is currently flowing in water
330 -- Number of seconds this item entity has existed so far
333 set_item
= function(self
, itemstring
)
334 self
.itemstring
= itemstring
335 if self
.itemstring
== "" then
336 -- item not yet known
339 local stack
= ItemStack(itemstring
)
340 local count
= stack
:get_count()
341 local max_count
= stack
:get_stack_max()
342 if count
> max_count
then
344 self
.itemstring
= stack
:get_name().." "..max_count
346 local s
= 0.2 + 0.1 * (count
/ max_count
)
348 local itemtable
= stack
:to_table()
350 local description
= ""
352 itemname
= stack
:to_table().name
354 local item_texture
= nil
356 if core
.registered_items
[itemname
] then
357 item_texture
= core
.registered_items
[itemname
].inventory_image
358 item_type
= core
.registered_items
[itemname
].type
359 description
= core
.registered_items
[itemname
].description
363 visual
= "wielditem",
364 textures
= {itemname
},
365 visual_size
= {x
= s
, y
= s
},
366 collisionbox
= {-c
, -c
, -c
, c
, c
, c
},
367 automatic_rotate
= math
.pi
* 0.5,
368 infotext
= description
,
370 self
.object
:set_properties(prop
)
371 if item_drop_settings
.random_item_velocity
== true then
372 minetest
.after(0, function(self
)
373 if not self
or not self
.object
or not self
.object
:get_luaentity() then
376 local vel
= self
.object
:getvelocity()
377 if vel
and vel
.x
== 0 and vel
.z
== 0 then
378 local x
= math
.random(1, 5)
379 if math
.random(1,2) == 1 then
382 local z
= math
.random(1, 5)
383 if math
.random(1,2) == 1 then
386 local y
= math
.random(2,4)
387 self
.object
:setvelocity({x
=1/x
, y
=y
, z
=1/z
})
394 get_staticdata
= function(self
)
395 return core
.serialize({
396 itemstring
= self
.itemstring
,
397 always_collect
= self
.always_collect
,
399 _insta_collect
= self
._insta_collect
,
400 _flowing
= self
._flowing
,
401 _removed
= self
._removed
,
405 on_activate
= function(self
, staticdata
, dtime_s
)
406 if string.sub(staticdata
, 1, string.len("return")) == "return" then
407 local data
= core
.deserialize(staticdata
)
408 if data
and type(data
) == "table" then
409 self
.itemstring
= data
.itemstring
410 self
.always_collect
= data
.always_collect
412 self
.age
= data
.age
+ dtime_s
416 --remember collection data
417 -- If true, can collect item without delay
418 self
._insta_collect
= data
._insta_collect
419 self
._flowing
= data
._flowing
420 self
._removed
= data
._removed
423 self
.itemstring
= staticdata
425 if self
._removed
then
430 if self
._insta_collect
== nil then
431 -- Intentionally default, since delayed collection is rare
432 self
._insta_collect
= true
434 if self
._flowing
== nil then
435 self
._flowing
= false
437 self
._magnet_timer
= 0
438 self
._magnet_active
= false
439 -- How long ago the last possible collector was detected. nil = none in this session
440 self
._collector_timer
= nil
441 -- Used to apply additional force
443 self
._forcestart
= nil
446 self
.object
:set_armor_groups({immortal
= 1})
447 self
.object
:setvelocity({x
= 0, y
= 2, z
= 0})
448 self
.object
:setacceleration({x
= 0, y
= -get_gravity(), z
= 0})
449 self
:set_item(self
.itemstring
)
452 try_merge_with
= function(self
, own_stack
, object
, entity
)
453 if self
.age
== entity
.age
or entity
._removed
then
454 -- Can not merge with itself and remove entity
458 local stack
= ItemStack(entity
.itemstring
)
459 local name
= stack
:get_name()
460 if own_stack
:get_name() ~= name
or
461 own_stack
:get_meta() ~= stack
:get_meta() or
462 own_stack
:get_wear() ~= stack
:get_wear() or
463 own_stack
:get_free_space() == 0 then
464 -- Can not merge different or full stack
468 local count
= own_stack
:get_count()
469 local total_count
= stack
:get_count() + count
470 local max_count
= stack
:get_stack_max()
472 if total_count
> max_count
then
475 -- Merge the remote stack into this one
477 local pos
= object
:get_pos()
478 pos
.y
= pos
.y
+ ((total_count
- count
) / max_count
) * 0.15
479 self
.object
:move_to(pos
)
481 self
.age
= 0 -- Handle as new entity
482 own_stack
:set_count(total_count
)
483 self
:set_item(own_stack
:to_string())
485 entity
._removed
= true
490 on_step
= function(self
, dtime
)
491 if self
._removed
then
494 self
.age
= self
.age
+ dtime
495 if self
._collector_timer
~= nil then
496 self
._collector_timer
= self
._collector_timer
+ dtime
498 if time_to_live
> 0 and self
.age
> time_to_live
then
503 -- Delete corrupted item entities. The itemstring MUST be non-empty on its first step,
504 -- otherwise there might have some data corruption.
505 if self
.itemstring
== "" then
506 minetest
.log("warning", "Item entity with empty itemstring found at "..minetest
.pos_to_string(self
.object
:get_pos()).. "! Deleting it now.")
511 local p
= self
.object
:get_pos()
512 local node
= core
.get_node_or_nil(p
)
513 local in_unloaded
= (node
== nil)
515 -- If no collector was found for a long enough time, declare the magnet as disabled
516 if self
._magnet_active
and (self
._collector_timer
== nil or (self
._collector_timer
> item_drop_settings
.magnet_time
)) then
517 self
._magnet_active
= false
518 enable_physics(self
.object
, self
)
522 -- Don't infinetly fall into unloaded map
523 disable_physics(self
.object
, self
)
527 -- Destroy item in lava or special nodes
529 local def
= minetest
.registered_nodes
[nn
]
530 if (def
and def
.groups
and (def
.groups
.lava
or def
.groups
.destroys_items
== 1)) then
531 -- Special effect for lava
532 if def
.groups
.lava
then
533 minetest
.sound_play("builtin_item_lava", {pos
= self
.object
:get_pos(), gain
= 0.5})
540 -- Push item out when stuck inside solid opaque node
541 if def
and def
.walkable
and def
.groups
and def
.groups
.opaque
== 1 then
547 -- First prepare the order in which the 4 sides are to be checked.
549 -- 2nd: other direction
550 -- 3rd and 4th: other axis
551 local cxcz
= function(o
, cw
, one
, zero
)
553 table.insert(o
, { [one
]=1, y
=0, [zero
]=0 })
554 table.insert(o
, { [one
]=-1, y
=0, [zero
]=0 })
556 table.insert(o
, { [one
]=-1, y
=0, [zero
]=0 })
557 table.insert(o
, { [one
]=1, y
=0, [zero
]=0 })
561 if math
.abs(cx
) > math
.abs(cz
) then
562 order
= cxcz(order
, cx
, "x", "z")
563 order
= cxcz(order
, cz
, "z", "x")
565 order
= cxcz(order
, cz
, "z", "x")
566 order
= cxcz(order
, cx
, "x", "z")
569 -- Check which one of the 4 sides is free
571 local nn
= minetest
.get_node(vector
.add(p
, order
[o
])).name
572 local def
= minetest
.registered_nodes
[nn
]
573 if def
and def
.walkable
== false and nn
~= "ignore" then
578 -- If none of the 4 sides is free, shoot upwards
579 if shootdir
== nil then
580 shootdir
= { x
=0, y
=1, z
=0 }
581 local nn
= minetest
.get_node(vector
.add(p
, shootdir
)).name
582 if nn
== "ignore" then
583 -- Do not push into ignore
588 -- Set new item moving speed accordingly
589 local newv
= vector
.multiply(shootdir
, 3)
590 self
.object
:setacceleration({x
= 0, y
= 0, z
= 0})
591 self
.object
:setvelocity(newv
)
593 disable_physics(self
.object
, self
, false, false)
595 if shootdir
.y
== 0 then
597 p
.x
= math
.floor(p
.x
)
598 p
.y
= math
.floor(p
.y
)
599 p
.z
= math
.floor(p
.z
)
606 -- This code is run after the entity got a push from above “push away” code.
607 -- It is responsible for making sure the entity is entirely outside the solid node
608 -- (with its full collision box), not just its center.
609 if self
._forcetimer
> 0 then
610 local cbox
= self
.object
:get_properties().collisionbox
612 if self
._force
.x
> 0 and (p
.x
> (self
._forcestart
.x
+ 0.5 + (cbox
[4] - cbox
[1])/2)) then ok
= true
613 elseif self
._force
.x
< 0 and (p
.x
< (self
._forcestart
.x
+ 0.5 - (cbox
[4] - cbox
[1])/2)) then ok
= true
614 elseif self
._force
.z
> 0 and (p
.z
> (self
._forcestart
.z
+ 0.5 + (cbox
[6] - cbox
[3])/2)) then ok
= true
615 elseif self
._force
.z
< 0 and (p
.z
< (self
._forcestart
.z
+ 0.5 - (cbox
[6] - cbox
[3])/2)) then ok
= true end
616 -- Item was successfully forced out. No more pushing
618 self
._forcetimer
= -1
620 enable_physics(self
.object
, self
)
622 self
._forcetimer
= self
._forcetimer
- dtime
625 elseif self
._force
then
627 enable_physics(self
.object
, self
)
631 -- Move item around on flowing liquids
632 if def
and def
.liquidtype
== "flowing" then
634 --[[ Get flowing direction (function call from flowlib), if there's a liquid.
635 NOTE: According to Qwertymine, flowlib.quickflow is only reliable for liquids with a flowing distance of 7.
636 Luckily, this is exactly what we need if we only care about water, which has this flowing distance. ]]
637 local vec
= flowlib
.quick_flow(p
, node
)
638 -- Just to make sure we don't manipulate the speed for no reason
639 if vec
.x
~= 0 or vec
.y
~= 0 or vec
.z
~= 0 then
640 -- Minecraft Wiki: Flowing speed is "about 1.39 meters per second"
642 -- Set new item moving speed into the direciton of the liquid
643 local newv
= vector
.multiply(vec
, f
)
644 self
.object
:setacceleration({x
= 0, y
= 0, z
= 0})
645 self
.object
:setvelocity({x
= newv
.x
, y
= -0.22, z
= newv
.z
})
647 self
.physical_state
= true
649 self
.object
:set_properties({
654 elseif self
._flowing
== true then
655 -- Disable flowing physics if not on/in flowing liquid
656 self
._flowing
= false
657 enable_physics(self
.object
, self
, true)
661 -- If node is not registered or node is walkably solid and resting on nodebox
662 local nn
= minetest
.get_node({x
=p
.x
, y
=p
.y
-0.5, z
=p
.z
}).name
663 local v
= self
.object
:getvelocity()
665 if not core
.registered_nodes
[nn
] or core
.registered_nodes
[nn
].walkable
and v
.y
== 0 then
666 if self
.physical_state
then
667 local own_stack
= ItemStack(self
.object
:get_luaentity().itemstring
)
668 -- Merge with close entities of the same item
669 for _
, object
in ipairs(core
.get_objects_inside_radius(p
, 0.8)) do
670 local obj
= object
:get_luaentity()
671 if obj
and obj
.name
== "__builtin:item"
672 and obj
.physical_state
== false then
673 if self
:try_merge_with(own_stack
, object
, obj
) then
678 disable_physics(self
.object
, self
)
681 if self
._magnet_active
== false then
682 enable_physics(self
.object
, self
)
687 -- Note: on_punch intentionally left out. The player should *not* be able to collect items by punching
690 if minetest
.settings
:get_bool("log_mods") then
691 minetest
.log("action", "mcl_item_entity loaded")