item_entity.lua 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. -- Minetest: builtin/item_entity.lua
  2. function core.spawn_item(pos, item)
  3. -- Take item in any format
  4. local stack = ItemStack(item)
  5. local obj = core.add_entity(pos, "__builtin:item")
  6. -- Don't use obj if it couldn't be added to the map.
  7. if obj then
  8. obj:get_luaentity():set_item(stack:to_string())
  9. end
  10. return obj
  11. end
  12. -- If item_entity_ttl is not set, enity will have default life time
  13. -- Setting it to -1 disables the feature
  14. local time_to_live = tonumber(core.settings:get("item_entity_ttl")) or 900
  15. local gravity = tonumber(core.settings:get("movement_gravity")) or 9.81
  16. core.register_entity(":__builtin:item", {
  17. initial_properties = {
  18. hp_max = 1,
  19. physical = true,
  20. collide_with_objects = false,
  21. collisionbox = {-0.3, -0.3, -0.3, 0.3, 0.3, 0.3},
  22. visual = "wielditem",
  23. visual_size = {x = 0.4, y = 0.4},
  24. textures = {""},
  25. spritediv = {x = 1, y = 1},
  26. initial_sprite_basepos = {x = 0, y = 0},
  27. is_visible = false,
  28. },
  29. itemstring = "",
  30. moving_state = true,
  31. slippery_state = false,
  32. age = 0,
  33. set_item = function(self, item)
  34. local stack = ItemStack(item or self.itemstring)
  35. self.itemstring = stack:to_string()
  36. if self.itemstring == "" then
  37. -- item not yet known
  38. return
  39. end
  40. -- Backwards compatibility: old clients use the texture
  41. -- to get the type of the item
  42. local itemname = stack:is_known() and stack:get_name() or "unknown"
  43. local max_count = stack:get_stack_max()
  44. local count = math.min(stack:get_count(), max_count)
  45. local size = 0.2 + 0.1 * (count / max_count) ^ (1 / 3)
  46. local coll_height = size * 0.75
  47. self.object:set_properties({
  48. is_visible = true,
  49. visual = "wielditem",
  50. textures = {itemname},
  51. visual_size = {x = size, y = size},
  52. collisionbox = {-size, -coll_height, -size,
  53. size, coll_height, size},
  54. selectionbox = {-size, -size, -size, size, size, size},
  55. automatic_rotate = math.pi * 0.5 * 0.2 / size,
  56. wield_item = self.itemstring,
  57. })
  58. end,
  59. get_staticdata = function(self)
  60. return core.serialize({
  61. itemstring = self.itemstring,
  62. age = self.age,
  63. dropped_by = self.dropped_by
  64. })
  65. end,
  66. on_activate = function(self, staticdata, dtime_s)
  67. if string.sub(staticdata, 1, string.len("return")) == "return" then
  68. local data = core.deserialize(staticdata)
  69. if data and type(data) == "table" then
  70. self.itemstring = data.itemstring
  71. self.age = (data.age or 0) + dtime_s
  72. self.dropped_by = data.dropped_by
  73. end
  74. else
  75. self.itemstring = staticdata
  76. end
  77. self.object:set_armor_groups({immortal = 1})
  78. self.object:set_velocity({x = 0, y = 2, z = 0})
  79. self.object:set_acceleration({x = 0, y = -gravity, z = 0})
  80. self:set_item()
  81. end,
  82. try_merge_with = function(self, own_stack, object, entity)
  83. if self.age == entity.age then
  84. -- Can not merge with itself
  85. return false
  86. end
  87. local stack = ItemStack(entity.itemstring)
  88. local name = stack:get_name()
  89. if own_stack:get_name() ~= name or
  90. own_stack:get_meta() ~= stack:get_meta() or
  91. own_stack:get_wear() ~= stack:get_wear() or
  92. own_stack:get_free_space() == 0 then
  93. -- Can not merge different or full stack
  94. return false
  95. end
  96. local count = own_stack:get_count()
  97. local total_count = stack:get_count() + count
  98. local max_count = stack:get_stack_max()
  99. if total_count > max_count then
  100. return false
  101. end
  102. -- Merge the remote stack into this one
  103. local pos = object:get_pos()
  104. pos.y = pos.y + ((total_count - count) / max_count) * 0.15
  105. self.object:move_to(pos)
  106. self.age = 0 -- Handle as new entity
  107. own_stack:set_count(total_count)
  108. self:set_item(own_stack)
  109. entity.itemstring = ""
  110. object:remove()
  111. return true
  112. end,
  113. on_step = function(self, dtime)
  114. self.age = self.age + dtime
  115. if time_to_live > 0 and self.age > time_to_live then
  116. self.itemstring = ""
  117. self.object:remove()
  118. return
  119. end
  120. local pos = self.object:get_pos()
  121. local node = core.get_node_or_nil({
  122. x = pos.x,
  123. y = pos.y + self.object:get_properties().collisionbox[2] - 0.05,
  124. z = pos.z
  125. })
  126. local vel = self.object:getvelocity()
  127. local def = node and core.registered_nodes[node.name]
  128. -- Ignore is nil -> stop until the block loaded
  129. local is_moving = (def and not def.walkable) or
  130. vel.x ~= 0 or vel.y ~= 0 or vel.z ~= 0
  131. local is_slippery = false
  132. if def and def.walkable then
  133. local slippery = core.get_item_group(node.name, "slippery")
  134. is_slippery = slippery ~= 0
  135. if is_slippery and (math.abs(vel.x) > 0.2 or math.abs(vel.z) > 0.2) then
  136. -- Horizontal deceleration
  137. local slip_factor = 4.0 / (slippery + 4)
  138. self.object:set_acceleration({
  139. x = -vel.x * slip_factor,
  140. y = 0,
  141. z = -vel.z * slip_factor
  142. })
  143. elseif vel.y == 0 then
  144. is_moving = false
  145. end
  146. end
  147. if self.moving_state == is_moving and
  148. self.slippery_state == is_slippery then
  149. -- Do not update anything until the moving state changes
  150. return
  151. end
  152. self.moving_state = is_moving
  153. self.slippery_state = is_slippery
  154. if is_moving then
  155. self.object:set_acceleration({x = 0, y = -gravity, z = 0})
  156. else
  157. self.object:set_acceleration({x = 0, y = 0, z = 0})
  158. self.object:set_velocity({x = 0, y = 0, z = 0})
  159. end
  160. --Only collect items if not moving
  161. if is_moving then
  162. return
  163. end
  164. -- Collect the items around to merge with
  165. local own_stack = ItemStack(self.itemstring)
  166. if own_stack:get_free_space() == 0 then
  167. return
  168. end
  169. local objects = core.get_objects_inside_radius(pos, 1.0)
  170. for k, obj in pairs(objects) do
  171. local entity = obj:get_luaentity()
  172. if entity and entity.name == "__builtin:item" then
  173. if self:try_merge_with(own_stack, obj, entity) then
  174. own_stack = ItemStack(self.itemstring)
  175. if own_stack:get_free_space() == 0 then
  176. return
  177. end
  178. end
  179. end
  180. end
  181. end,
  182. on_punch = function(self, hitter)
  183. local inv = hitter:get_inventory()
  184. if inv and self.itemstring ~= "" then
  185. local left = inv:add_item("main", self.itemstring)
  186. if left and not left:is_empty() then
  187. self:set_item(left)
  188. return
  189. end
  190. end
  191. self.itemstring = ""
  192. self.object:remove()
  193. end,
  194. })