init.lua 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  1. -- Minetest: builtin/item_entity.lua
  2. -- override ice to make slippery for 0.4.16
  3. if not minetest.raycast then
  4. minetest.override_item("default:ice", {
  5. groups = {cracky = 3, puts_out_fire = 1, cools_lava = 1, slippery = 3}})
  6. end
  7. function core.spawn_item(pos, item)
  8. local stack = ItemStack(item)
  9. local obj = core.add_entity(pos, "__builtin:item")
  10. if obj then
  11. obj:get_luaentity():set_item(stack:to_string())
  12. end
  13. return obj
  14. end
  15. -- If item_entity_ttl is not set, enity will have default life time
  16. -- Setting it to -1 disables the feature
  17. local time_to_live = tonumber(core.settings:get("item_entity_ttl")) or 900
  18. local gravity = tonumber(core.settings:get("movement_gravity")) or 9.81
  19. local destroy_item = core.settings:get_bool("destroy_item") ~= false
  20. -- water flow functions by QwertyMine3, edited by TenPlus1
  21. local inv_roots = {
  22. [0] = 1
  23. }
  24. local function to_unit_vector(dir_vector)
  25. local sum = dir_vector.x * dir_vector.x + dir_vector.z * dir_vector.z
  26. local invr_sum = 0
  27. -- find inverse square root if possible
  28. if inv_roots[sum] ~= nil then
  29. invr_sum = inv_roots[sum]
  30. else
  31. -- not found, compute and save the inverse square root
  32. invr_sum = 1.0 / math.sqrt(sum)
  33. inv_roots[sum] = invr_sum
  34. end
  35. return {
  36. x = dir_vector.x * invr_sum,
  37. y = dir_vector.y,
  38. z = dir_vector.z * invr_sum
  39. }
  40. end
  41. local function node_ok(pos)
  42. local node = minetest.get_node_or_nil(pos)
  43. if node and minetest.registered_nodes[node.name] then
  44. return node
  45. end
  46. return minetest.registered_nodes["default:dirt"]
  47. end
  48. local function quick_flow_logic(node, pos_testing, direction)
  49. local node_testing = node_ok(pos_testing)
  50. local param2 = node.param2
  51. if not minetest.registered_nodes[node.name].groups.liquid then
  52. param2 = 0
  53. end
  54. if minetest.registered_nodes[node_testing.name].liquidtype ~= "flowing"
  55. and minetest.registered_nodes[node_testing.name].liquidtype ~= "source" then
  56. return 0
  57. end
  58. local param2_testing = node_testing.param2
  59. if param2_testing < param2 then
  60. if (param2 - param2_testing) > 6 then
  61. return -direction
  62. else
  63. return direction
  64. end
  65. elseif param2_testing > param2 then
  66. if (param2_testing - param2) > 6 then
  67. return direction
  68. else
  69. return -direction
  70. end
  71. end
  72. return 0
  73. end
  74. -- reciprocal of the length of an unit square's diagonal
  75. local DIAG_WEIGHT = 2 / math.sqrt(2)
  76. local function quick_flow(pos, node)
  77. local x, z = 0.0, 0.0
  78. x = x + quick_flow_logic(node, {x = pos.x - 1, y = pos.y, z = pos.z},-1)
  79. x = x + quick_flow_logic(node, {x = pos.x + 1, y = pos.y, z = pos.z}, 1)
  80. z = z + quick_flow_logic(node, {x = pos.x, y = pos.y, z = pos.z - 1},-1)
  81. z = z + quick_flow_logic(node, {x = pos.x, y = pos.y, z = pos.z + 1}, 1)
  82. return to_unit_vector({x = x, y = 0, z = z})
  83. end
  84. -- END water flow functions
  85. -- particle effects for when item is destroyed
  86. local function add_effects(pos)
  87. minetest.add_particlespawner({
  88. amount = 1,
  89. time = 0.25,
  90. minpos = pos,
  91. maxpos = pos,
  92. minvel = {x = -1, y = 2, z = -1},
  93. maxvel = {x = 1, y = 4, z = 1},
  94. minacc = {x = 0, y = 0, z = 0},
  95. maxacc = {x = 0, y = 0, z = 0},
  96. minexptime = 1,
  97. maxexptime = 3,
  98. minsize = 1,
  99. maxsize = 4,
  100. texture = "tnt_smoke.png",
  101. })
  102. end
  103. local water_force = 0.8
  104. local water_friction = 0.8
  105. local dry_friction = 2.5
  106. core.register_entity(":__builtin:item", {
  107. initial_properties = {
  108. hp_max = 1,
  109. physical = true,
  110. collide_with_objects = false,
  111. collisionbox = {-0.3, -0.3, -0.3, 0.3, 0.3, 0.3},
  112. visual = "wielditem",
  113. visual_size = {x = 0.4, y = 0.4},
  114. textures = {""},
  115. spritediv = {x = 1, y = 1},
  116. initial_sprite_basepos = {x = 0, y = 0},
  117. is_visible = false,
  118. infotext = "",
  119. },
  120. itemstring = "",
  121. moving_state = true,
  122. slippery_state = false,
  123. age = 0,
  124. set_item = function(self, item)
  125. local stack = ItemStack(item or self.itemstring)
  126. self.itemstring = stack:to_string()
  127. if self.itemstring == "" then
  128. return
  129. end
  130. local itemname = stack:is_known() and stack:get_name() or "unknown"
  131. local max_count = stack:get_stack_max()
  132. local count = math.min(stack:get_count(), max_count)
  133. local size = 0.2 + 0.1 * (count / max_count) ^ (1 / 3)
  134. local col_height = size * 0.75
  135. local def = core.registered_nodes[itemname]
  136. local glow = def and def.light_source
  137. local c1, c2 = "",""
  138. if not(stack:get_count() == 1) then
  139. c1 = " x"..tostring(stack:get_count())
  140. c2 = " "..tostring(stack:get_count())
  141. end
  142. local name1 = stack:get_meta():get_string("description")
  143. local name
  144. if name1 == "" then
  145. name = core.registered_items[itemname].description
  146. else
  147. name = name1
  148. end
  149. -- small random size bias to counter Z-fighting
  150. local bias = math.random() * 1e-3
  151. self.object:set_properties({
  152. is_visible = true,
  153. visual = "wielditem",
  154. textures = {itemname},
  155. visual_size = {x = size + bias, y = size + bias, z = size + bias},
  156. collisionbox = {-size, -col_height, -size, size, col_height, size},
  157. selectionbox = {-size, -size, -size, size, size, size},
  158. automatic_rotate = 0.314 / size,
  159. wield_item = self.itemstring,
  160. glow = glow,
  161. infotext = name .. c1 .. "\n(" .. itemname .. c2 .. ")"
  162. })
  163. end,
  164. get_staticdata = function(self)
  165. return core.serialize({
  166. itemstring = self.itemstring,
  167. age = self.age,
  168. dropped_by = self.dropped_by
  169. })
  170. end,
  171. on_activate = function(self, staticdata, dtime_s)
  172. if string.sub(staticdata, 1, string.len("return")) == "return" then
  173. local data = core.deserialize(staticdata)
  174. if data and type(data) == "table" then
  175. self.itemstring = data.itemstring
  176. self.age = (data.age or 0) + dtime_s
  177. self.dropped_by = data.dropped_by
  178. end
  179. else
  180. self.itemstring = staticdata
  181. end
  182. self.object:set_armor_groups({immortal = 1})
  183. self.object:set_velocity({x = 0, y = 2, z = 0})
  184. self.object:set_acceleration({x = 0, y = -gravity, z = 0})
  185. self:set_item()
  186. end,
  187. try_merge_with = function(self, own_stack, object, entity)
  188. if self.age == entity.age then
  189. return false -- Can not merge with itself
  190. end
  191. local stack = ItemStack(entity.itemstring)
  192. local name = stack:get_name()
  193. if own_stack:get_name() ~= name
  194. or own_stack:get_meta() ~= stack:get_meta()
  195. or own_stack:get_wear() ~= stack:get_wear()
  196. or own_stack:get_free_space() == 0 then
  197. return false -- Can not merge different or full stack
  198. end
  199. local count = own_stack:get_count()
  200. local total_count = stack:get_count() + count
  201. local max_count = stack:get_stack_max()
  202. if total_count > max_count then
  203. return false
  204. end
  205. -- Merge the remote stack into this one
  206. local pos = object:get_pos()
  207. pos.y = pos.y + ((total_count - count) / max_count) * 0.15
  208. self.object:move_to(pos)
  209. self.age = 0 -- Handle as new entity
  210. own_stack:set_count(total_count)
  211. self:set_item(own_stack)
  212. entity.itemstring = ""
  213. object:remove()
  214. return true
  215. end,
  216. on_step = function(self, dtime, moveresult)
  217. local pos = self.object:get_pos()
  218. self.age = self.age + dtime
  219. if time_to_live > 0 and self.age > time_to_live then
  220. self.itemstring = ""
  221. self.object:remove()
  222. add_effects(pos)
  223. return
  224. end
  225. -- get nodes every 1/4 second
  226. self.timer = (self.timer or 0) + dtime
  227. if self.timer > 0.25 or not self.node_inside then
  228. self.node_inside = minetest.get_node_or_nil(pos)
  229. self.def_inside = self.node_inside
  230. and core.registered_nodes[self.node_inside.name]
  231. -- get ground node for collision
  232. self.node_under = nil
  233. if moveresult.touching_ground then
  234. for _, info in ipairs(moveresult.collisions) do
  235. if info.axis == "y" then
  236. self.node_under = core.get_node(info.node_pos)
  237. break
  238. end
  239. end
  240. end
  241. self.def_under = self.node_under
  242. and core.registered_nodes[self.node_under.name]
  243. self.timer = 0
  244. end
  245. local node = self.node_inside
  246. -- Delete in 'ignore' nodes
  247. if node and node.name == "ignore" then
  248. self.itemstring = ""
  249. self.object:remove()
  250. return
  251. end
  252. -- do custom step function
  253. local name = ItemStack(self.itemstring):get_name() or ""
  254. local custom = core.registered_items[name]
  255. and core.registered_items[name].dropped_step
  256. if custom and custom(self, pos, dtime) == false then
  257. return -- skip further checks if false
  258. end
  259. local vel = self.object:get_velocity()
  260. local def = self.def_inside
  261. local is_slippery = false
  262. local is_moving = (def and not def.walkable) or
  263. vel.x ~= 0 or vel.y ~= 0 or vel.z ~= 0
  264. -- destroy item when dropped into lava (if enabled)
  265. if destroy_item and def and def.groups and def.groups.lava then
  266. minetest.sound_play("builtin_item_lava", {
  267. pos = pos,
  268. max_hear_distance = 6,
  269. gain = 0.5
  270. })
  271. self.itemstring = ""
  272. self.object:remove()
  273. add_effects(pos)
  274. return
  275. end
  276. -- water flowing
  277. if def and def.liquidtype == "flowing" then
  278. -- force applies on acceleration over time, thus multiply
  279. local force = water_force * dtime
  280. -- friction applies on velocity over time, thus exponentiate
  281. local friction = (1.0 + water_friction) ^ dtime
  282. -- get flow velocity and current vel/acc state
  283. local vec = quick_flow(pos, node)
  284. local a = self.object:get_acceleration()
  285. self.object:set_acceleration({
  286. x = a.x + vec.x * force,
  287. y = a.y,
  288. z = a.z + vec.z * force
  289. })
  290. -- apply friction to prevent items going too fast, and also to make
  291. -- water flow override previous horizontal momentum more quickly
  292. local v = self.object:get_velocity()
  293. -- adjust friction for going against the current
  294. local v_horz = { x = v.x, y = 0, z = v.z }
  295. local v_dir = to_unit_vector(v_horz)
  296. local flow_dot = v_dir.x * vec.x + v_dir.y * vec.y
  297. -- also maps flow_dot from [-1,0] to [0.5,2.5]
  298. friction = 1.0 + ((friction - 1.0) * (flow_dot + 1.5))
  299. self.object:set_velocity({
  300. x = v.x / friction,
  301. y = v.y / friction,
  302. z = v.z / friction
  303. })
  304. return
  305. end
  306. -- item inside block, move to vacant space
  307. if def and (def.walkable == nil or def.walkable == true)
  308. and (def.collision_box == nil or def.collision_box.type == "regular")
  309. and (def.node_box == nil or def.node_box.type == "regular") then
  310. local npos = minetest.find_node_near(pos, 1, "air")
  311. if npos then
  312. self.object:move_to(npos)
  313. end
  314. self.node_inside = nil -- force get_node
  315. return
  316. end
  317. -- Switch locals to node under
  318. node = self.node_under
  319. def = self.def_under
  320. -- Slippery node check
  321. if def and def.walkable then
  322. local slippery = core.get_item_group(node.name, "slippery")
  323. is_slippery = slippery ~= 0
  324. if is_slippery and (math.abs(vel.x) > 0.2 or math.abs(vel.z) > 0.2) then
  325. -- Horizontal deceleration
  326. local slip_factor = 4.0 / (slippery + 4)
  327. self.object:set_acceleration({
  328. x = -vel.x * slip_factor,
  329. y = 0,
  330. z = -vel.z * slip_factor
  331. })
  332. elseif vel.y == 0 then
  333. is_moving = false
  334. end
  335. end
  336. if self.moving_state == is_moving
  337. and self.slippery_state == is_slippery then
  338. return -- No further updates until moving state changes
  339. end
  340. self.moving_state = is_moving
  341. self.slippery_state = is_slippery
  342. local a_curr = self.object:get_acceleration()
  343. local v_curr = self.object:get_velocity()
  344. if is_moving then
  345. self.object:set_acceleration({
  346. x = a_curr.x,
  347. y = a_curr.y - gravity,
  348. z = a_curr.z
  349. })
  350. else
  351. self.object:set_acceleration({x = 0, y = 0, z = 0})
  352. -- preserve *some* velocity so items don't get stuck on the very ledges
  353. -- of nodes once they move just enough to leave the hitbox of flowing water
  354. self.object:set_velocity({
  355. x = v_curr.x / dry_friction,
  356. y = v_curr.y / dry_friction,
  357. z = v_curr.z / dry_friction
  358. })
  359. end
  360. --Only collect items if not moving
  361. if is_moving then
  362. return
  363. end
  364. -- Collect the items around to merge with
  365. local own_stack = ItemStack(self.itemstring)
  366. if own_stack:get_free_space() == 0 then
  367. return
  368. end
  369. local objects = core.get_objects_inside_radius(pos, 1.0)
  370. for k, obj in pairs(objects) do
  371. local entity = obj:get_luaentity()
  372. if entity and entity.name == "__builtin:item" then
  373. if self:try_merge_with(own_stack, obj, entity) then
  374. own_stack = ItemStack(self.itemstring)
  375. if own_stack:get_free_space() == 0 then
  376. return
  377. end
  378. end
  379. end
  380. end
  381. end,
  382. on_punch = function(self, hitter)
  383. local inv = hitter:get_inventory()
  384. if inv and self.itemstring ~= "" then
  385. local left = inv:add_item("main", self.itemstring)
  386. if left and not left:is_empty() then
  387. self:set_item(left)
  388. return
  389. end
  390. end
  391. self.itemstring = ""
  392. self.object:remove()
  393. end,
  394. })