Update helptext of obsidian
[MineClone/MineClone2.git] / mods / ENTITIES / mcl_item_entity / init.lua
blobd7c76f19911691d3786de327dc40d7150b8b50da
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: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
69 -- Collection
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", {
75 pos = pos,
76 max_hear_distance = 16,
77 gain = 1.0,
78 }, true)
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:get_pos()
99 local vec = vector.subtract(checkpos, opos)
100 vec = vector.add(opos, vector.divide(vec, 2))
101 object:move_to(vec)
104 --fix eternally falling items
105 minetest.after(0, function(object)
106 local lua = object:get_luaentity()
107 if lua then
108 object:set_acceleration({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,
132 }, true)
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", true)
216 if (digger ~= nil and minetest.is_creative_enabled(digger:get_player_name())) 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
249 if type(item) == "string" then
250 count = ItemStack(item):get_count()
251 else
252 count = item:get_count()
254 local drop_item = ItemStack(item)
255 drop_item:set_count(1)
256 for i=1,count do
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
260 dpos.y = dpos.y + 1
262 -- Spawn item and apply random speed
263 local obj = minetest.add_item(dpos, drop_item)
264 if obj ~= nil then
265 local x = math.random(1, 5)
266 if math.random(1,2) == 1 then
267 x = -x
269 local z = math.random(1, 5)
270 if math.random(1,2) == 1 then
271 z = -z
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
286 cs = 1
288 local item = itemstack:take_item(cs)
289 local obj = minetest.add_item(p, item)
290 if obj then
291 v.x = v.x*4
292 v.y = v.y*4 + 2
293 v.z = v.z*4
294 obj:set_velocity(v)
295 -- Force collection delay
296 obj:get_luaentity()._insta_collect = false
297 return itemstack
302 --modify builtin:item
304 local time_to_live = tonumber(minetest.settings:get("item_entity_ttl"))
305 if not time_to_live then
306 time_to_live = 300
309 minetest.register_entity(":__builtin:item", {
310 initial_properties = {
311 hp_max = 1,
312 physical = true,
313 collide_with_objects = false,
314 collisionbox = {-0.3, -0.3, -0.3, 0.3, 0.3, 0.3},
315 pointable = false,
316 visual = "wielditem",
317 visual_size = {x = 0.4, y = 0.4},
318 textures = {""},
319 spritediv = {x = 1, y = 1},
320 initial_sprite_basepos = {x = 0, y = 0},
321 is_visible = false,
322 infotext = "",
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.
329 itemstring = '',
331 -- If true, item will fall
332 physical_state = true,
334 -- If item entity is currently flowing in water
335 _flowing = false,
337 -- Number of seconds this item entity has existed so far
338 age = 0,
340 set_item = function(self, itemstring)
341 self.itemstring = itemstring
342 if self.itemstring == "" then
343 -- item not yet known
344 return
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
350 count = max_count
351 self.itemstring = stack:get_name().." "..max_count
353 local itemtable = stack:to_table()
354 local itemname = nil
355 local description = ""
356 if itemtable then
357 itemname = stack:to_table().name
359 local item_texture = nil
360 local item_type = ""
361 local glow
362 local def = minetest.registered_items[itemname]
363 if def then
364 item_texture = def.inventory_image
365 item_type = def.type
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
371 local c = s
372 s = s / wield_scale
373 local prop = {
374 is_visible = true,
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,
381 glow = glow,
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
387 return
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
393 x = -x
395 local z = math.random(1, 5)
396 if math.random(1,2) == 1 then
397 z = -z
399 local y = math.random(2,4)
400 self.object:set_velocity({x=1/x, y=y, z=1/z})
402 end, self)
405 end,
407 get_staticdata = function(self)
408 return minetest.serialize({
409 itemstring = self.itemstring,
410 always_collect = self.always_collect,
411 age = self.age,
412 _insta_collect = self._insta_collect,
413 _flowing = self._flowing,
414 _removed = self._removed,
416 end,
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
424 if data.age then
425 self.age = data.age + dtime_s
426 else
427 self.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
435 else
436 self.itemstring = staticdata
438 if self._removed then
439 self._removed = true
440 self.object:remove()
441 return
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
455 self._force = nil
456 self._forcestart = nil
457 self._forcetimer = 0
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)
463 end,
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
468 return false
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
478 return false
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
486 return false
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
499 object:remove()
500 return true
501 end,
503 on_step = function(self, dtime)
504 if self._removed then
505 return
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
512 self._removed = true
513 self.object:remove()
514 return
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.")
520 self._removed = true
521 self.object:remove()
522 return
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)
533 return
535 if in_unloaded then
536 -- Don't infinetly fall into unloaded map
537 disable_physics(self.object, self)
538 return
541 -- Destroy item in lava, fire or special nodes
542 local nn = node.name
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
548 if dg ~= 2 then
549 minetest.sound_play("builtin_item_lava", {pos = self.object:get_pos(), gain = 0.5}, true)
551 self._removed = true
552 self.object:remove()
553 return
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
558 local shootdir
559 local cx = (p.x % 1) - 0.5
560 local cz = (p.z % 1) - 0.5
561 local order = {}
563 -- First prepare the order in which the 4 sides are to be checked.
564 -- 1st: closest
565 -- 2nd: other direction
566 -- 3rd and 4th: other axis
567 local cxcz = function(o, cw, one, zero)
568 if cw < 0 then
569 table.insert(o, { [one]=1, y=0, [zero]=0 })
570 table.insert(o, { [one]=-1, y=0, [zero]=0 })
571 else
572 table.insert(o, { [one]=-1, y=0, [zero]=0 })
573 table.insert(o, { [one]=1, y=0, [zero]=0 })
575 return o
577 if math.abs(cx) < math.abs(cz) then
578 order = cxcz(order, cx, "x", "z")
579 order = cxcz(order, cz, "z", "x")
580 else
581 order = cxcz(order, cz, "z", "x")
582 order = cxcz(order, cx, "x", "z")
585 -- Check which one of the 4 sides is free
586 for o=1, #order do
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
590 shootdir = order[o]
591 break
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
600 return
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
612 self._force = newv
613 p.x = math.floor(p.x)
614 p.y = math.floor(p.y)
615 p.z = math.floor(p.z)
616 self._forcestart = p
617 self._forcetimer = 1
619 return
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
627 local ok = false
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
633 if ok then
634 self._forcetimer = -1
635 self._force = nil
636 enable_physics(self.object, self)
637 else
638 self._forcetimer = self._forcetimer - dtime
640 return
641 elseif self._force then
642 self._force = nil
643 enable_physics(self.object, self)
644 return
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"
657 local f = 1.39
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
664 self._flowing = true
665 self.object:set_properties({
666 physical = true
668 return
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)
674 return
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
690 return
694 disable_physics(self.object, self)
696 else
697 if self._magnet_active == false then
698 enable_physics(self.object, self)
701 end,
703 -- Note: on_punch intentionally left out. The player should *not* be able to collect items by punching