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
:set_acceleration({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", true)
216 if (digger
~= nil and minetest
.is_creative_enabled(digger
:get_player_name())) 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
250 count
= ItemStack(item
):get_count()
252 count
= item
:get_count()
254 local drop_item
= ItemStack(item
)
255 drop_item
:set_count(1)
257 local dpos
= table.copy(pos
)
258 -- Apply offset for plantlike_rooted nodes because of their special shape
259 if nodedef
and nodedef
.drawtype
== "plantlike_rooted" and nodedef
.walkable
then
262 -- Spawn item and apply random speed
263 local obj
= minetest
.add_item(dpos
, drop_item
)
265 local x
= math
.random(1, 5)
266 if math
.random(1,2) == 1 then
269 local z
= math
.random(1, 5)
270 if math
.random(1,2) == 1 then
273 obj
:set_velocity({x
=1/x
, y
=obj
:get_velocity().y
, z
=1/z
})
279 -- Drop single items by default
280 function minetest
.item_drop(itemstack
, dropper
, pos
)
281 if dropper
and dropper
:is_player() then
282 local v
= dropper
:get_look_dir()
283 local p
= {x
=pos
.x
, y
=pos
.y
+1.2, z
=pos
.z
}
284 local cs
= itemstack
:get_count()
285 if dropper
:get_player_control().sneak
then
288 local item
= itemstack
:take_item(cs
)
289 local obj
= minetest
.add_item(p
, item
)
295 -- Force collection delay
296 obj
:get_luaentity()._insta_collect
= false
302 --modify builtin:item
304 local time_to_live
= tonumber(minetest
.settings
:get("item_entity_ttl"))
305 if not time_to_live
then
309 minetest
.register_entity(":__builtin:item", {
310 initial_properties
= {
313 collide_with_objects
= false,
314 collisionbox
= {-0.3, -0.3, -0.3, 0.3, 0.3, 0.3},
316 visual
= "wielditem",
317 visual_size
= {x
= 0.4, y
= 0.4},
319 spritediv
= {x
= 1, y
= 1},
320 initial_sprite_basepos
= {x
= 0, y
= 0},
325 -- Itemstring of dropped item. The empty string is used when the item is not yet initialized yet.
326 -- The itemstring MUST be set immediately to a non-empty string after creating the entity.
327 -- The hand is NOT permitted as dropped item. ;-)
328 -- Item entities will be deleted if they still have an empty itemstring on their first on_step tick.
331 -- If true, item will fall
332 physical_state
= true,
334 -- If item entity is currently flowing in water
337 -- Number of seconds this item entity has existed so far
340 set_item
= function(self
, itemstring
)
341 self
.itemstring
= itemstring
342 if self
.itemstring
== "" then
343 -- item not yet known
346 local stack
= ItemStack(itemstring
)
347 local count
= stack
:get_count()
348 local max_count
= stack
:get_stack_max()
349 if count
> max_count
then
351 self
.itemstring
= stack
:get_name().." "..max_count
353 local itemtable
= stack
:to_table()
355 local description
= ""
357 itemname
= stack
:to_table().name
359 local item_texture
= nil
362 local def
= minetest
.registered_items
[itemname
]
364 item_texture
= def
.inventory_image
366 description
= def
.description
367 glow
= def
.light_source
369 local s
= 0.2 + 0.1 * (count
/ max_count
)
370 local wield_scale
= (def
and def
.wield_scale
and def
.wield_scale
.x
) or 1
375 visual
= "wielditem",
376 textures
= {itemname
},
377 visual_size
= {x
= s
, y
= s
},
378 collisionbox
= {-c
, -c
, -c
, c
, c
, c
},
379 automatic_rotate
= math
.pi
* 0.5,
380 infotext
= description
,
383 self
.object
:set_properties(prop
)
384 if item_drop_settings
.random_item_velocity
== true then
385 minetest
.after(0, function(self
)
386 if not self
or not self
.object
or not self
.object
:get_luaentity() then
389 local vel
= self
.object
:get_velocity()
390 if vel
and vel
.x
== 0 and vel
.z
== 0 then
391 local x
= math
.random(1, 5)
392 if math
.random(1,2) == 1 then
395 local z
= math
.random(1, 5)
396 if math
.random(1,2) == 1 then
399 local y
= math
.random(2,4)
400 self
.object
:set_velocity({x
=1/x
, y
=y
, z
=1/z
})
407 get_staticdata
= function(self
)
408 return minetest
.serialize({
409 itemstring
= self
.itemstring
,
410 always_collect
= self
.always_collect
,
412 _insta_collect
= self
._insta_collect
,
413 _flowing
= self
._flowing
,
414 _removed
= self
._removed
,
418 on_activate
= function(self
, staticdata
, dtime_s
)
419 if string.sub(staticdata
, 1, string.len("return")) == "return" then
420 local data
= minetest
.deserialize(staticdata
)
421 if data
and type(data
) == "table" then
422 self
.itemstring
= data
.itemstring
423 self
.always_collect
= data
.always_collect
425 self
.age
= data
.age
+ dtime_s
429 --remember collection data
430 -- If true, can collect item without delay
431 self
._insta_collect
= data
._insta_collect
432 self
._flowing
= data
._flowing
433 self
._removed
= data
._removed
436 self
.itemstring
= staticdata
438 if self
._removed
then
443 if self
._insta_collect
== nil then
444 -- Intentionally default, since delayed collection is rare
445 self
._insta_collect
= true
447 if self
._flowing
== nil then
448 self
._flowing
= false
450 self
._magnet_timer
= 0
451 self
._magnet_active
= false
452 -- How long ago the last possible collector was detected. nil = none in this session
453 self
._collector_timer
= nil
454 -- Used to apply additional force
456 self
._forcestart
= nil
459 self
.object
:set_armor_groups({immortal
= 1})
460 self
.object
:set_velocity({x
= 0, y
= 2, z
= 0})
461 self
.object
:set_acceleration({x
= 0, y
= -get_gravity(), z
= 0})
462 self
:set_item(self
.itemstring
)
465 try_merge_with
= function(self
, own_stack
, object
, entity
)
466 if self
.age
== entity
.age
or entity
._removed
then
467 -- Can not merge with itself and remove entity
471 local stack
= ItemStack(entity
.itemstring
)
472 local name
= stack
:get_name()
473 if own_stack
:get_name() ~= name
or
474 own_stack
:get_meta() ~= stack
:get_meta() or
475 own_stack
:get_wear() ~= stack
:get_wear() or
476 own_stack
:get_free_space() == 0 then
477 -- Can not merge different or full stack
481 local count
= own_stack
:get_count()
482 local total_count
= stack
:get_count() + count
483 local max_count
= stack
:get_stack_max()
485 if total_count
> max_count
then
488 -- Merge the remote stack into this one
490 local pos
= object
:get_pos()
491 pos
.y
= pos
.y
+ ((total_count
- count
) / max_count
) * 0.15
492 self
.object
:move_to(pos
)
494 self
.age
= 0 -- Handle as new entity
495 own_stack
:set_count(total_count
)
496 self
:set_item(own_stack
:to_string())
498 entity
._removed
= true
503 on_step
= function(self
, dtime
)
504 if self
._removed
then
507 self
.age
= self
.age
+ dtime
508 if self
._collector_timer
~= nil then
509 self
._collector_timer
= self
._collector_timer
+ dtime
511 if time_to_live
> 0 and self
.age
> time_to_live
then
516 -- Delete corrupted item entities. The itemstring MUST be non-empty on its first step,
517 -- otherwise there might have some data corruption.
518 if self
.itemstring
== "" then
519 minetest
.log("warning", "Item entity with empty itemstring found at "..minetest
.pos_to_string(self
.object
:get_pos()).. "! Deleting it now.")
525 local p
= self
.object
:get_pos()
526 local node
= minetest
.get_node_or_nil(p
)
527 local in_unloaded
= (node
== nil)
529 -- If no collector was found for a long enough time, declare the magnet as disabled
530 if self
._magnet_active
and (self
._collector_timer
== nil or (self
._collector_timer
> item_drop_settings
.magnet_time
)) then
531 self
._magnet_active
= false
532 enable_physics(self
.object
, self
)
536 -- Don't infinetly fall into unloaded map
537 disable_physics(self
.object
, self
)
541 -- Destroy item in lava, fire or special nodes
543 local def
= minetest
.registered_nodes
[nn
]
544 local lg
= minetest
.get_item_group(nn
, "lava")
545 local fg
= minetest
.get_item_group(nn
, "fire")
546 local dg
= minetest
.get_item_group(nn
, "destroys_items")
547 if (def
and (lg
~= 0 or fg
~= 0 or dg
== 1)) then
549 minetest
.sound_play("builtin_item_lava", {pos
= self
.object
:get_pos(), gain
= 0.5}, true)
556 -- Push item out when stuck inside solid opaque node
557 if def
and def
.walkable
and def
.groups
and def
.groups
.opaque
== 1 then
559 local cx
= (p
.x
% 1) - 0.5
560 local cz
= (p
.z
% 1) - 0.5
563 -- First prepare the order in which the 4 sides are to be checked.
565 -- 2nd: other direction
566 -- 3rd and 4th: other axis
567 local cxcz
= function(o
, cw
, one
, zero
)
569 table.insert(o
, { [one
]=1, y
=0, [zero
]=0 })
570 table.insert(o
, { [one
]=-1, y
=0, [zero
]=0 })
572 table.insert(o
, { [one
]=-1, y
=0, [zero
]=0 })
573 table.insert(o
, { [one
]=1, y
=0, [zero
]=0 })
577 if math
.abs(cx
) < math
.abs(cz
) then
578 order
= cxcz(order
, cx
, "x", "z")
579 order
= cxcz(order
, cz
, "z", "x")
581 order
= cxcz(order
, cz
, "z", "x")
582 order
= cxcz(order
, cx
, "x", "z")
585 -- Check which one of the 4 sides is free
587 local nn
= minetest
.get_node(vector
.add(p
, order
[o
])).name
588 local def
= minetest
.registered_nodes
[nn
]
589 if def
and def
.walkable
== false and nn
~= "ignore" then
594 -- If none of the 4 sides is free, shoot upwards
595 if shootdir
== nil then
596 shootdir
= { x
=0, y
=1, z
=0 }
597 local nn
= minetest
.get_node(vector
.add(p
, shootdir
)).name
598 if nn
== "ignore" then
599 -- Do not push into ignore
604 -- Set new item moving speed accordingly
605 local newv
= vector
.multiply(shootdir
, 3)
606 self
.object
:set_acceleration({x
= 0, y
= 0, z
= 0})
607 self
.object
:set_velocity(newv
)
609 disable_physics(self
.object
, self
, false, false)
611 if shootdir
.y
== 0 then
613 p
.x
= math
.floor(p
.x
)
614 p
.y
= math
.floor(p
.y
)
615 p
.z
= math
.floor(p
.z
)
622 -- This code is run after the entity got a push from above “push away” code.
623 -- It is responsible for making sure the entity is entirely outside the solid node
624 -- (with its full collision box), not just its center.
625 if self
._forcetimer
> 0 then
626 local cbox
= self
.object
:get_properties().collisionbox
628 if self
._force
.x
> 0 and (p
.x
> (self
._forcestart
.x
+ 0.5 + (cbox
[4] - cbox
[1])/2)) then ok
= true
629 elseif self
._force
.x
< 0 and (p
.x
< (self
._forcestart
.x
+ 0.5 - (cbox
[4] - cbox
[1])/2)) then ok
= true
630 elseif self
._force
.z
> 0 and (p
.z
> (self
._forcestart
.z
+ 0.5 + (cbox
[6] - cbox
[3])/2)) then ok
= true
631 elseif self
._force
.z
< 0 and (p
.z
< (self
._forcestart
.z
+ 0.5 - (cbox
[6] - cbox
[3])/2)) then ok
= true end
632 -- Item was successfully forced out. No more pushing
634 self
._forcetimer
= -1
636 enable_physics(self
.object
, self
)
638 self
._forcetimer
= self
._forcetimer
- dtime
641 elseif self
._force
then
643 enable_physics(self
.object
, self
)
647 -- Move item around on flowing liquids
648 if def
and def
.liquidtype
== "flowing" then
650 --[[ Get flowing direction (function call from flowlib), if there's a liquid.
651 NOTE: According to Qwertymine, flowlib.quickflow is only reliable for liquids with a flowing distance of 7.
652 Luckily, this is exactly what we need if we only care about water, which has this flowing distance. ]]
653 local vec
= flowlib
.quick_flow(p
, node
)
654 -- Just to make sure we don't manipulate the speed for no reason
655 if vec
.x
~= 0 or vec
.y
~= 0 or vec
.z
~= 0 then
656 -- Minecraft Wiki: Flowing speed is "about 1.39 meters per second"
658 -- Set new item moving speed into the direciton of the liquid
659 local newv
= vector
.multiply(vec
, f
)
660 self
.object
:set_acceleration({x
= 0, y
= 0, z
= 0})
661 self
.object
:set_velocity({x
= newv
.x
, y
= -0.22, z
= newv
.z
})
663 self
.physical_state
= true
665 self
.object
:set_properties({
670 elseif self
._flowing
== true then
671 -- Disable flowing physics if not on/in flowing liquid
672 self
._flowing
= false
673 enable_physics(self
.object
, self
, true)
677 -- If node is not registered or node is walkably solid and resting on nodebox
678 local nn
= minetest
.get_node({x
=p
.x
, y
=p
.y
-0.5, z
=p
.z
}).name
679 local v
= self
.object
:get_velocity()
681 if not minetest
.registered_nodes
[nn
] or minetest
.registered_nodes
[nn
].walkable
and v
.y
== 0 then
682 if self
.physical_state
then
683 local own_stack
= ItemStack(self
.object
:get_luaentity().itemstring
)
684 -- Merge with close entities of the same item
685 for _
, object
in ipairs(minetest
.get_objects_inside_radius(p
, 0.8)) do
686 local obj
= object
:get_luaentity()
687 if obj
and obj
.name
== "__builtin:item"
688 and obj
.physical_state
== false then
689 if self
:try_merge_with(own_stack
, object
, obj
) then
694 disable_physics(self
.object
, self
)
697 if self
._magnet_active
== false then
698 enable_physics(self
.object
, self
)
703 -- Note: on_punch intentionally left out. The player should *not* be able to collect items by punching