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
:getpos()
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
:getpos()) <= 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
:getpos()
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 player
= args
[1]
119 local object
= args
[2]
120 local lua
= object
:get_luaentity()
121 if player
== nil or not player
:is_player() or object
== nil or lua
== nil or lua
.itemstring
== nil then
124 if inv
:room_for_item("main", ItemStack(object
:get_luaentity().itemstring
)) then
125 inv
:add_item("main", ItemStack(object
:get_luaentity().itemstring
))
126 if not object
:get_luaentity()._removed
then
127 minetest
.sound_play("item_drop_pickup", {
129 max_hear_distance
= 16,
133 check_pickup_achievements(object
, player
)
134 object
:get_luaentity()._removed
= true
137 enable_physics(object
, object
:get_luaentity())
139 end, {player
, object
})
145 if not collected
then
146 if object
:get_luaentity()._magnet_timer
> 1 then
147 object
:get_luaentity()._magnet_timer
= -item_drop_settings
.magnet_time
148 object
:get_luaentity()._magnet_active
= false
149 elseif object
:get_luaentity()._magnet_timer
< 0 then
150 object
:get_luaentity()._magnet_timer
= object
:get_luaentity()._magnet_timer
+ dtime
161 local minigroups
= { "shearsy", "swordy", "shearsy_wool", "swordy_cobweb" }
162 local basegroups
= { "pickaxey", "axey", "shovely" }
163 local materials
= { "wood", "gold", "stone", "iron", "diamond" }
165 -- Checks if the given node would drop its useful drop if dug by a tool
166 -- with the given tool capabilities. Returns true if it will yield its useful
167 -- drop, false otherwise.
168 local check_can_drop
= function(node_name
, tool_capabilities
)
169 local handy
= minetest
.get_item_group(node_name
, "handy")
170 local dig_immediate
= minetest
.get_item_group(node_name
, "dig_immediate")
171 if handy
== 1 or dig_immediate
== 2 or dig_immediate
== 3 then
175 if tool_capabilities
then
176 toolgroupcaps
= tool_capabilities
.groupcaps
181 -- Compare node groups with tool capabilities
182 for m
=1, #minigroups
do
183 local minigroup
= minigroups
[m
]
184 local g
= minetest
.get_item_group(node_name
, minigroup
)
186 local plus
= minigroup
.. "_dig"
187 if toolgroupcaps
[plus
] then
192 for b
=1, #basegroups
do
193 local basegroup
= basegroups
[b
]
194 local g
= minetest
.get_item_group(node_name
, basegroup
)
196 for m
=g
, #materials
do
197 local plus
= basegroup
.. "_dig_"..materials
[m
]
198 if toolgroupcaps
[plus
] then
209 function minetest
.handle_node_drops(pos
, drops
, digger
)
210 -- NOTE: This function override allows digger to be nil.
211 -- This means there is no digger. This is a special case which allows this function to be called
212 -- by hand. Creative Mode is intentionally ignored in this case.
214 local doTileDrops
= minetest
.settings
:get_bool("mcl_doTileDrops") or true
215 if (digger
~= nil and minetest
.settings
:get_bool("creative_mode")) or doTileDrops
== false then
219 -- Check if node will yield its useful drop by the digger's tool
220 local dug_node
= minetest
.get_node(pos
)
222 if digger
~= nil then
223 local tool
= digger
:get_wielded_item()
224 toolcaps
= tool
:get_tool_capabilities()
226 if not check_can_drop(dug_node
.name
, toolcaps
) then
231 --[[ Special node drops when dug by shears by reading _mcl_shears_drop
232 from the node definition.
233 Definition of _mcl_shears_drop:
234 * true: Drop itself when dug by shears
235 * table: Drop every itemstring in this table when dub by shears
237 local nodedef
= minetest
.registered_nodes
[dug_node
.name
]
238 if toolcaps
~= nil and toolcaps
.groupcaps
and toolcaps
.groupcaps
.shearsy_dig
and nodedef
._mcl_shears_drop
then
239 if nodedef
._mcl_shears_drop
== true then
240 drops
= { dug_node
.name
}
242 drops
= nodedef
._mcl_shears_drop
246 for _
,item
in ipairs(drops
) do
248 if type(item
) == "string" then
252 count
= item
:get_count()
253 name
= item
:get_name()
256 local obj
= core
.add_item(pos
, name
)
258 local x
= math
.random(1, 5)
259 if math
.random(1,2) == 1 then
262 local z
= math
.random(1, 5)
263 if math
.random(1,2) == 1 then
266 obj
:setvelocity({x
=1/x
, y
=obj
:getvelocity().y
, z
=1/z
})
272 -- Drop single items by default
273 function minetest
.item_drop(itemstack
, dropper
, pos
)
274 if dropper
and dropper
:is_player() then
275 local v
= dropper
:get_look_dir()
276 local p
= {x
=pos
.x
, y
=pos
.y
+1.2, z
=pos
.z
}
277 local cs
= itemstack
:get_count()
278 if dropper
:get_player_control().sneak
then
281 local item
= itemstack
:take_item(cs
)
282 local obj
= core
.add_item(p
, item
)
288 -- Force collection delay
289 obj
:get_luaentity()._insta_collect
= false
295 --modify builtin:item
297 local time_to_live
= tonumber(core
.setting_get("item_entity_ttl"))
298 if not time_to_live
then
302 core
.register_entity(":__builtin:item", {
303 initial_properties
= {
306 collide_with_objects
= false,
307 collisionbox
= {-0.3, -0.3, -0.3, 0.3, 0.3, 0.3},
308 visual
= "wielditem",
309 visual_size
= {x
= 0.4, y
= 0.4},
311 spritediv
= {x
= 1, y
= 1},
312 initial_sprite_basepos
= {x
= 0, y
= 0},
317 -- Itemstring of dropped item. The empty string is used when the item is not yet initialized yet.
318 -- The itemstring MUST be set immediately to a non-empty string after creating the entity.
319 -- The hand is NOT permitted as dropped item. ;-)
320 -- Item entities will be deleted if they still have an empty itemstring on their first on_step tick.
323 -- If true, item will fall
324 physical_state
= true,
326 -- If item entity is currently flowing in water
329 -- Number of seconds this item entity has existed so far
332 set_item
= function(self
, itemstring
)
333 self
.itemstring
= itemstring
334 if self
.itemstring
== "" then
335 -- item not yet known
338 local stack
= ItemStack(itemstring
)
339 local count
= stack
:get_count()
340 local max_count
= stack
:get_stack_max()
341 if count
> max_count
then
343 self
.itemstring
= stack
:get_name().." "..max_count
345 local s
= 0.2 + 0.1 * (count
/ max_count
)
347 local itemtable
= stack
:to_table()
349 local description
= ""
351 itemname
= stack
:to_table().name
353 local item_texture
= nil
355 if core
.registered_items
[itemname
] then
356 item_texture
= core
.registered_items
[itemname
].inventory_image
357 item_type
= core
.registered_items
[itemname
].type
358 description
= core
.registered_items
[itemname
].description
362 visual
= "wielditem",
363 textures
= {itemname
},
364 visual_size
= {x
= s
, y
= s
},
365 collisionbox
= {-c
, -c
, -c
, c
, c
, c
},
366 automatic_rotate
= math
.pi
* 0.5,
367 infotext
= description
,
369 self
.object
:set_properties(prop
)
370 if item_drop_settings
.random_item_velocity
== true then
371 minetest
.after(0, function(self
)
372 if not self
or not self
.object
or not self
.object
:get_luaentity() then
375 local vel
= self
.object
:getvelocity()
376 if vel
and vel
.x
== 0 and vel
.z
== 0 then
377 local x
= math
.random(1, 5)
378 if math
.random(1,2) == 1 then
381 local z
= math
.random(1, 5)
382 if math
.random(1,2) == 1 then
385 local y
= math
.random(2,4)
386 self
.object
:setvelocity({x
=1/x
, y
=y
, z
=1/z
})
393 get_staticdata
= function(self
)
394 return core
.serialize({
395 itemstring
= self
.itemstring
,
396 always_collect
= self
.always_collect
,
398 _insta_collect
= self
._insta_collect
,
399 _flowing
= self
._flowing
,
400 _removed
= self
._removed
,
404 on_activate
= function(self
, staticdata
, dtime_s
)
405 if string.sub(staticdata
, 1, string.len("return")) == "return" then
406 local data
= core
.deserialize(staticdata
)
407 if data
and type(data
) == "table" then
408 self
.itemstring
= data
.itemstring
409 self
.always_collect
= data
.always_collect
411 self
.age
= data
.age
+ dtime_s
415 --remember collection data
416 -- If true, can collect item without delay
417 self
._insta_collect
= data
._insta_collect
418 self
._flowing
= data
._flowing
419 self
._removed
= data
._removed
422 self
.itemstring
= staticdata
424 if self
._removed
then
429 if self
._insta_collect
== nil then
430 -- Intentionally default, since delayed collection is rare
431 self
._insta_collect
= true
433 if self
._flowing
== nil then
434 self
._flowing
= false
436 self
._magnet_timer
= 0
437 self
._magnet_active
= false
438 -- How long ago the last possible collector was detected. nil = none in this session
439 self
._collector_timer
= nil
440 -- Used to apply additional force
442 self
._forcestart
= nil
445 self
.object
:set_armor_groups({immortal
= 1})
446 self
.object
:setvelocity({x
= 0, y
= 2, z
= 0})
447 self
.object
:setacceleration({x
= 0, y
= -get_gravity(), z
= 0})
448 self
:set_item(self
.itemstring
)
451 try_merge_with
= function(self
, own_stack
, object
, entity
)
452 if self
.age
== entity
.age
or entity
._removed
then
453 -- Can not merge with itself and remove entity
457 local stack
= ItemStack(entity
.itemstring
)
458 local name
= stack
:get_name()
459 if own_stack
:get_name() ~= name
or
460 own_stack
:get_meta() ~= stack
:get_meta() or
461 own_stack
:get_wear() ~= stack
:get_wear() or
462 own_stack
:get_free_space() == 0 then
463 -- Can not merge different or full stack
467 local count
= own_stack
:get_count()
468 local total_count
= stack
:get_count() + count
469 local max_count
= stack
:get_stack_max()
471 if total_count
> max_count
then
474 -- Merge the remote stack into this one
476 local pos
= object
:get_pos()
477 pos
.y
= pos
.y
+ ((total_count
- count
) / max_count
) * 0.15
478 self
.object
:move_to(pos
)
480 self
.age
= 0 -- Handle as new entity
481 own_stack
:set_count(total_count
)
482 self
:set_item(own_stack
:to_string())
484 entity
._removed
= true
489 on_step
= function(self
, dtime
)
490 if self
._removed
then
493 self
.age
= self
.age
+ dtime
494 if self
._collector_timer
~= nil then
495 self
._collector_timer
= self
._collector_timer
+ dtime
497 if time_to_live
> 0 and self
.age
> time_to_live
then
502 -- Delete corrupted item entities. The itemstring MUST be non-empty on its first step,
503 -- otherwise there might have some data corruption.
504 if self
.itemstring
== "" then
505 minetest
.log("warning", "Item entity with empty itemstring found at "..minetest
.pos_to_string(self
.object
:getpos()).. "! Deleting it now.")
510 local p
= self
.object
:getpos()
511 local node
= core
.get_node_or_nil(p
)
512 local in_unloaded
= (node
== nil)
514 -- If no collector was found for a long enough time, declare the magnet as disabled
515 if self
._magnet_active
and (self
._collector_timer
== nil or (self
._collector_timer
> item_drop_settings
.magnet_time
)) then
516 self
._magnet_active
= false
517 enable_physics(self
.object
, self
)
521 -- Don't infinetly fall into unloaded map
522 disable_physics(self
.object
, self
)
526 -- Destroy item in lava or special nodes
528 local def
= minetest
.registered_nodes
[nn
]
529 if (def
and def
.groups
and (def
.groups
.lava
or def
.groups
.destroys_items
== 1)) then
530 -- Special effect for lava
531 if def
.groups
.lava
then
532 minetest
.sound_play("builtin_item_lava", {pos
= self
.object
:getpos(), gain
= 0.5})
539 -- Push item out when stuck inside solid opaque node
540 if def
and def
.walkable
and def
.groups
and def
.groups
.opaque
== 1 then
546 -- First prepare the order in which the 4 sides are to be checked.
548 -- 2nd: other direction
549 -- 3rd and 4th: other axis
550 local cxcz
= function(o
, cw
, one
, zero
)
552 table.insert(o
, { [one
]=1, y
=0, [zero
]=0 })
553 table.insert(o
, { [one
]=-1, y
=0, [zero
]=0 })
555 table.insert(o
, { [one
]=-1, y
=0, [zero
]=0 })
556 table.insert(o
, { [one
]=1, y
=0, [zero
]=0 })
560 if math
.abs(cx
) > math
.abs(cz
) then
561 order
= cxcz(order
, cx
, "x", "z")
562 order
= cxcz(order
, cz
, "z", "x")
564 order
= cxcz(order
, cz
, "z", "x")
565 order
= cxcz(order
, cx
, "x", "z")
568 -- Check which one of the 4 sides is free
570 local nn
= minetest
.get_node(vector
.add(p
, order
[o
])).name
571 local def
= minetest
.registered_nodes
[nn
]
572 if def
and def
.walkable
== false and nn
~= "ignore" then
577 -- If none of the 4 sides is free, shoot upwards
578 if shootdir
== nil then
579 shootdir
= { x
=0, y
=1, z
=0 }
580 local nn
= minetest
.get_node(vector
.add(p
, shootdir
)).name
581 if nn
== "ignore" then
582 -- Do not push into ignore
587 -- Set new item moving speed accordingly
588 local newv
= vector
.multiply(shootdir
, 3)
589 self
.object
:setacceleration({x
= 0, y
= 0, z
= 0})
590 self
.object
:setvelocity(newv
)
592 disable_physics(self
.object
, self
, false, false)
594 if shootdir
.y
== 0 then
596 p
.x
= math
.floor(p
.x
)
597 p
.y
= math
.floor(p
.y
)
598 p
.z
= math
.floor(p
.z
)
605 -- This code is run after the entity got a push from above “push away” code.
606 -- It is responsible for making sure the entity is entirely outside the solid node
607 -- (with its full collision box), not just its center.
608 if self
._forcetimer
> 0 then
609 local cbox
= self
.object
:get_properties().collisionbox
611 if self
._force
.x
> 0 and (p
.x
> (self
._forcestart
.x
+ 0.5 + (cbox
[4] - cbox
[1])/2)) then ok
= true
612 elseif self
._force
.x
< 0 and (p
.x
< (self
._forcestart
.x
+ 0.5 - (cbox
[4] - cbox
[1])/2)) then ok
= true
613 elseif self
._force
.z
> 0 and (p
.z
> (self
._forcestart
.z
+ 0.5 + (cbox
[6] - cbox
[3])/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 end
615 -- Item was successfully forced out. No more pushing
617 self
._forcetimer
= -1
619 enable_physics(self
.object
, self
)
621 self
._forcetimer
= self
._forcetimer
- dtime
624 elseif self
._force
then
626 enable_physics(self
.object
, self
)
630 -- Move item around on flowing liquids
631 if def
and def
.liquidtype
== "flowing" then
633 --[[ Get flowing direction (function call from flowlib), if there's a liquid.
634 NOTE: According to Qwertymine, flowlib.quickflow is only reliable for liquids with a flowing distance of 7.
635 Luckily, this is exactly what we need if we only care about water, which has this flowing distance. ]]
636 local vec
= flowlib
.quick_flow(p
, node
)
637 -- Just to make sure we don't manipulate the speed for no reason
638 if vec
.x
~= 0 or vec
.y
~= 0 or vec
.z
~= 0 then
639 -- Minecraft Wiki: Flowing speed is "about 1.39 meters per second"
641 -- Set new item moving speed into the direciton of the liquid
642 local newv
= vector
.multiply(vec
, f
)
643 self
.object
:setacceleration({x
= 0, y
= 0, z
= 0})
644 self
.object
:setvelocity({x
= newv
.x
, y
= -0.22, z
= newv
.z
})
646 self
.physical_state
= true
648 self
.object
:set_properties({
653 elseif self
._flowing
== true then
654 -- Disable flowing physics if not on/in flowing liquid
655 self
._flowing
= false
656 enable_physics(self
.object
, self
, true)
660 -- If node is not registered or node is walkably solid and resting on nodebox
661 local nn
= minetest
.get_node({x
=p
.x
, y
=p
.y
-0.5, z
=p
.z
}).name
662 local v
= self
.object
:getvelocity()
664 if not core
.registered_nodes
[nn
] or core
.registered_nodes
[nn
].walkable
and v
.y
== 0 then
665 if self
.physical_state
then
666 local own_stack
= ItemStack(self
.object
:get_luaentity().itemstring
)
667 -- Merge with close entities of the same item
668 for _
, object
in ipairs(core
.get_objects_inside_radius(p
, 0.8)) do
669 local obj
= object
:get_luaentity()
670 if obj
and obj
.name
== "__builtin:item"
671 and obj
.physical_state
== false then
672 if self
:try_merge_with(own_stack
, object
, obj
) then
677 disable_physics(self
.object
, self
)
680 if self
._magnet_active
== false then
681 enable_physics(self
.object
, self
)
686 -- Note: on_punch intentionally left out. The player should *not* be able to collect items by punching
689 if minetest
.settings
:get_bool("log_mods") then
690 minetest
.log("action", "mcl_item_entity loaded")