Version 0.42.1
[MineClone/MineClone2.git] / mods / ENTITIES / mcl_item_entity / init.lua
blobfa587bf2f4d4ebef39d7db26c224e17a23c3cbc4
1 --basic settings
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
16 end
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")
28 end
29 end
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({
35 physical = true
37 object:set_velocity({x=0,y=0,z=0})
38 object:set_acceleration({x=0,y=-get_gravity(),z=0})
39 end
40 end
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({
46 physical = false
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})
51 end
52 end
53 end
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
69 -- Collection
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", {
75 pos = pos,
76 max_hear_distance = 16,
77 gain = 1.0,
79 check_pickup_achievements(object, player)
82 -- Destroy entity
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
85 object:remove()
86 collected = true
87 end
89 -- Magnet
90 else
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))
101 object:moveto(vec)
104 --fix eternally falling items
105 minetest.after(0, function(object)
106 local lua = object:get_luaentity()
107 if lua then
108 object:setacceleration({x=0, y=0, z=0})
110 end, object)
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
123 return
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", {
129 pos = pos,
130 max_hear_distance = 16,
131 gain = 1.0,
134 check_pickup_achievements(object, player)
135 object:get_luaentity()._removed = true
136 object:remove()
137 else
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
160 end)
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
173 return true
174 else
175 local toolgroupcaps
176 if tool_capabilities then
177 toolgroupcaps = tool_capabilities.groupcaps
178 else
179 return false
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)
186 if g ~= 0 then
187 local plus = minigroup .. "_dig"
188 if toolgroupcaps[plus] then
189 return true
193 for b=1, #basegroups do
194 local basegroup = basegroups[b]
195 local g = minetest.get_item_group(node_name, basegroup)
196 if g ~= 0 then
197 for m=g, #materials do
198 local plus = basegroup .. "_dig_"..materials[m]
199 if toolgroupcaps[plus] then
200 return true
206 return false
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
217 return
220 -- Check if node will yield its useful drop by the digger's tool
221 local dug_node = minetest.get_node(pos)
222 local toolcaps
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
228 return
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 }
242 else
243 drops = nodedef._mcl_shears_drop
247 for _,item in ipairs(drops) do
248 local count, name
249 if type(item) == "string" then
250 count = 1
251 name = item
252 else
253 count = item:get_count()
254 name = item:get_name()
256 for i=1,count do
257 local obj = core.add_item(pos, name)
258 if obj ~= nil then
259 local x = math.random(1, 5)
260 if math.random(1,2) == 1 then
261 x = -x
263 local z = math.random(1, 5)
264 if math.random(1,2) == 1 then
265 z = -z
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
280 cs = 1
282 local item = itemstack:take_item(cs)
283 local obj = core.add_item(p, item)
284 if obj then
285 v.x = v.x*4
286 v.y = v.y*4 + 2
287 v.z = v.z*4
288 obj:setvelocity(v)
289 -- Force collection delay
290 obj:get_luaentity()._insta_collect = false
291 return itemstack
296 --modify builtin:item
298 local time_to_live = tonumber(minetest.settings:get("item_entity_ttl"))
299 if not time_to_live then
300 time_to_live = 300
303 core.register_entity(":__builtin:item", {
304 initial_properties = {
305 hp_max = 1,
306 physical = true,
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},
311 textures = {""},
312 spritediv = {x = 1, y = 1},
313 initial_sprite_basepos = {x = 0, y = 0},
314 is_visible = false,
315 infotext = "",
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.
322 itemstring = '',
324 -- If true, item will fall
325 physical_state = true,
327 -- If item entity is currently flowing in water
328 _flowing = false,
330 -- Number of seconds this item entity has existed so far
331 age = 0,
333 set_item = function(self, itemstring)
334 self.itemstring = itemstring
335 if self.itemstring == "" then
336 -- item not yet known
337 return
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
343 count = max_count
344 self.itemstring = stack:get_name().." "..max_count
346 local s = 0.2 + 0.1 * (count / max_count)
347 local c = s
348 local itemtable = stack:to_table()
349 local itemname = nil
350 local description = ""
351 if itemtable then
352 itemname = stack:to_table().name
354 local item_texture = nil
355 local item_type = ""
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
361 local prop = {
362 is_visible = true,
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
374 return
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
380 x = -x
382 local z = math.random(1, 5)
383 if math.random(1,2) == 1 then
384 z = -z
386 local y = math.random(2,4)
387 self.object:setvelocity({x=1/x, y=y, z=1/z})
389 end, self)
392 end,
394 get_staticdata = function(self)
395 return core.serialize({
396 itemstring = self.itemstring,
397 always_collect = self.always_collect,
398 age = self.age,
399 _insta_collect = self._insta_collect,
400 _flowing = self._flowing,
401 _removed = self._removed,
403 end,
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
411 if data.age then
412 self.age = data.age + dtime_s
413 else
414 self.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
422 else
423 self.itemstring = staticdata
425 if self._removed then
426 self._removed = true
427 self.object:remove()
428 return
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
442 self._force = nil
443 self._forcestart = nil
444 self._forcetimer = 0
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)
450 end,
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
455 return false
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
465 return false
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
473 return false
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
486 object:remove()
487 return true
488 end,
490 on_step = function(self, dtime)
491 if self._removed then
492 return
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
499 self._removed = true
500 self.object:remove()
501 return
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:getpos()).. "! Deleting it now.")
507 self._removed = true
508 self.object:remove()
511 local p = self.object:getpos()
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)
519 return
521 if in_unloaded then
522 -- Don't infinetly fall into unloaded map
523 disable_physics(self.object, self)
524 return
527 -- Destroy item in lava or special nodes
528 local nn = node.name
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:getpos(), gain = 0.5})
535 self._removed = true
536 self.object:remove()
537 return
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
542 local shootdir
543 local cx = p.x % 1
544 local cz = p.z % 1
545 local order = {}
547 -- First prepare the order in which the 4 sides are to be checked.
548 -- 1st: closest
549 -- 2nd: other direction
550 -- 3rd and 4th: other axis
551 local cxcz = function(o, cw, one, zero)
552 if cw > 0 then
553 table.insert(o, { [one]=1, y=0, [zero]=0 })
554 table.insert(o, { [one]=-1, y=0, [zero]=0 })
555 else
556 table.insert(o, { [one]=-1, y=0, [zero]=0 })
557 table.insert(o, { [one]=1, y=0, [zero]=0 })
559 return o
561 if math.abs(cx) > math.abs(cz) then
562 order = cxcz(order, cx, "x", "z")
563 order = cxcz(order, cz, "z", "x")
564 else
565 order = cxcz(order, cz, "z", "x")
566 order = cxcz(order, cx, "x", "z")
569 -- Check which one of the 4 sides is free
570 for o=1, #order do
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
574 shootdir = order[o]
575 break
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
584 return
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
596 self._force = newv
597 p.x = math.floor(p.x)
598 p.y = math.floor(p.y)
599 p.z = math.floor(p.z)
600 self._forcestart = p
601 self._forcetimer = 1
603 return
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
611 local ok = false
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
617 if ok then
618 self._forcetimer = -1
619 self._force = nil
620 enable_physics(self.object, self)
621 else
622 self._forcetimer = self._forcetimer - dtime
624 return
625 elseif self._force then
626 self._force = nil
627 enable_physics(self.object, self)
628 return
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"
641 local f = 1.39
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
648 self._flowing = true
649 self.object:set_properties({
650 physical = true
652 return
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)
658 return
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
674 return
678 disable_physics(self.object, self)
680 else
681 if self._magnet_active == false then
682 enable_physics(self.object, self)
685 end,
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")