falling.lua 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  1. -- Minetest: builtin/item.lua
  2. local builtin_shared = ...
  3. local SCALE = 0.667
  4. local facedir_to_euler = {
  5. {y = 0, x = 0, z = 0},
  6. {y = -math.pi/2, x = 0, z = 0},
  7. {y = math.pi, x = 0, z = 0},
  8. {y = math.pi/2, x = 0, z = 0},
  9. {y = math.pi/2, x = -math.pi/2, z = math.pi/2},
  10. {y = math.pi/2, x = math.pi, z = math.pi/2},
  11. {y = math.pi/2, x = math.pi/2, z = math.pi/2},
  12. {y = math.pi/2, x = 0, z = math.pi/2},
  13. {y = -math.pi/2, x = math.pi/2, z = math.pi/2},
  14. {y = -math.pi/2, x = 0, z = math.pi/2},
  15. {y = -math.pi/2, x = -math.pi/2, z = math.pi/2},
  16. {y = -math.pi/2, x = math.pi, z = math.pi/2},
  17. {y = 0, x = 0, z = math.pi/2},
  18. {y = 0, x = -math.pi/2, z = math.pi/2},
  19. {y = 0, x = math.pi, z = math.pi/2},
  20. {y = 0, x = math.pi/2, z = math.pi/2},
  21. {y = math.pi, x = math.pi, z = math.pi/2},
  22. {y = math.pi, x = math.pi/2, z = math.pi/2},
  23. {y = math.pi, x = 0, z = math.pi/2},
  24. {y = math.pi, x = -math.pi/2, z = math.pi/2},
  25. {y = math.pi, x = math.pi, z = 0},
  26. {y = -math.pi/2, x = math.pi, z = 0},
  27. {y = 0, x = math.pi, z = 0},
  28. {y = math.pi/2, x = math.pi, z = 0}
  29. }
  30. local gravity = tonumber(core.settings:get("movement_gravity")) or 9.81
  31. --
  32. -- Falling stuff
  33. --
  34. core.register_entity(":__builtin:falling_node", {
  35. initial_properties = {
  36. visual = "item",
  37. visual_size = vector.new(SCALE, SCALE, SCALE),
  38. textures = {},
  39. physical = true,
  40. is_visible = false,
  41. collide_with_objects = true,
  42. collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
  43. },
  44. node = {},
  45. meta = {},
  46. floats = false,
  47. set_node = function(self, node, meta)
  48. node.param2 = node.param2 or 0
  49. self.node = node
  50. meta = meta or {}
  51. if type(meta.to_table) == "function" then
  52. meta = meta:to_table()
  53. end
  54. for _, list in pairs(meta.inventory or {}) do
  55. for i, stack in pairs(list) do
  56. if type(stack) == "userdata" then
  57. list[i] = stack:to_string()
  58. end
  59. end
  60. end
  61. local def = core.registered_nodes[node.name]
  62. if not def then
  63. -- Don't allow unknown nodes to fall
  64. core.log("info",
  65. "Unknown falling node removed at "..
  66. core.pos_to_string(self.object:get_pos()))
  67. self.object:remove()
  68. return
  69. end
  70. self.meta = meta
  71. -- Cache whether we're supposed to float on water
  72. self.floats = core.get_item_group(node.name, "float") ~= 0
  73. -- Save liquidtype for falling water
  74. self.liquidtype = def.liquidtype
  75. -- Set entity visuals
  76. if def.drawtype == "torchlike" or def.drawtype == "signlike" then
  77. local textures
  78. if def.tiles and def.tiles[1] then
  79. local tile = def.tiles[1]
  80. if type(tile) == "table" then
  81. tile = tile.name
  82. end
  83. if def.drawtype == "torchlike" then
  84. textures = { "("..tile..")^[transformFX", tile }
  85. else
  86. textures = { tile, "("..tile..")^[transformFX" }
  87. end
  88. end
  89. local vsize
  90. if def.visual_scale then
  91. local s = def.visual_scale
  92. vsize = vector.new(s, s, s)
  93. end
  94. self.object:set_properties({
  95. is_visible = true,
  96. visual = "upright_sprite",
  97. visual_size = vsize,
  98. textures = textures,
  99. glow = def.light_source,
  100. })
  101. elseif def.drawtype ~= "airlike" then
  102. local itemstring = node.name
  103. if core.is_colored_paramtype(def.paramtype2) then
  104. itemstring = core.itemstring_with_palette(itemstring, node.param2)
  105. end
  106. -- FIXME: solution needed for paramtype2 == "leveled"
  107. -- Calculate size of falling node
  108. local s = {}
  109. s.x = (def.visual_scale or 1) * SCALE
  110. s.y = s.x
  111. s.z = s.x
  112. -- Compensate for wield_scale
  113. if def.wield_scale then
  114. s.x = s.x / def.wield_scale.x
  115. s.y = s.y / def.wield_scale.y
  116. s.z = s.z / def.wield_scale.z
  117. end
  118. self.object:set_properties({
  119. is_visible = true,
  120. wield_item = itemstring,
  121. visual_size = s,
  122. glow = def.light_source,
  123. })
  124. end
  125. -- Set collision box (certain nodeboxes only for now)
  126. local nb_types = {fixed=true, leveled=true, connected=true}
  127. if def.drawtype == "nodebox" and def.node_box and
  128. nb_types[def.node_box.type] and def.node_box.fixed then
  129. local box = table.copy(def.node_box.fixed)
  130. if type(box[1]) == "table" then
  131. box = #box == 1 and box[1] or nil -- We can only use a single box
  132. end
  133. if box then
  134. if def.paramtype2 == "leveled" and (self.node.level or 0) > 0 then
  135. box[5] = -0.5 + self.node.level / 64
  136. end
  137. self.object:set_properties({
  138. collisionbox = box
  139. })
  140. end
  141. end
  142. -- Rotate entity
  143. if def.drawtype == "torchlike" then
  144. if (def.paramtype2 == "wallmounted" or def.paramtype2 == "colorwallmounted")
  145. and node.param2 % 8 == 7 then
  146. self.object:set_yaw(-math.pi*0.25)
  147. else
  148. self.object:set_yaw(math.pi*0.25)
  149. end
  150. elseif ((node.param2 ~= 0 or def.drawtype == "nodebox" or def.drawtype == "mesh")
  151. and (def.wield_image == "" or def.wield_image == nil))
  152. or def.drawtype == "signlike"
  153. or def.drawtype == "mesh"
  154. or def.drawtype == "normal"
  155. or def.drawtype == "nodebox" then
  156. if (def.paramtype2 == "facedir" or def.paramtype2 == "colorfacedir") then
  157. local fdir = node.param2 % 32 % 24
  158. -- Get rotation from a precalculated lookup table
  159. local euler = facedir_to_euler[fdir + 1]
  160. if euler then
  161. self.object:set_rotation(euler)
  162. end
  163. elseif (def.paramtype2 == "4dir" or def.paramtype2 == "color4dir") then
  164. local fdir = node.param2 % 4
  165. -- Get rotation from a precalculated lookup table
  166. local euler = facedir_to_euler[fdir + 1]
  167. if euler then
  168. self.object:set_rotation(euler)
  169. end
  170. elseif (def.drawtype ~= "plantlike" and def.drawtype ~= "plantlike_rooted" and
  171. (def.paramtype2 == "wallmounted" or def.paramtype2 == "colorwallmounted" or def.drawtype == "signlike")) then
  172. local rot = node.param2 % 8
  173. if (def.drawtype == "signlike" and def.paramtype2 ~= "wallmounted" and def.paramtype2 ~= "colorwallmounted") then
  174. -- Change rotation to "floor" by default for non-wallmounted paramtype2
  175. rot = 1
  176. end
  177. local pitch, yaw, roll = 0, 0, 0
  178. if def.drawtype == "nodebox" or def.drawtype == "mesh" then
  179. if rot == 0 then
  180. pitch, yaw = math.pi/2, 0
  181. elseif rot == 1 then
  182. pitch, yaw = -math.pi/2, math.pi
  183. elseif rot == 2 then
  184. pitch, yaw = 0, math.pi/2
  185. elseif rot == 3 then
  186. pitch, yaw = 0, -math.pi/2
  187. elseif rot == 4 then
  188. pitch, yaw = 0, math.pi
  189. elseif rot == 6 then
  190. pitch, yaw = math.pi/2, 0
  191. elseif rot == 7 then
  192. pitch, yaw = -math.pi/2, math.pi
  193. end
  194. else
  195. if rot == 1 then
  196. pitch, yaw = math.pi, math.pi
  197. elseif rot == 2 then
  198. pitch, yaw = math.pi/2, math.pi/2
  199. elseif rot == 3 then
  200. pitch, yaw = math.pi/2, -math.pi/2
  201. elseif rot == 4 then
  202. pitch, yaw = math.pi/2, math.pi
  203. elseif rot == 5 then
  204. pitch, yaw = math.pi/2, 0
  205. elseif rot == 6 then
  206. pitch, yaw = math.pi, -math.pi/2
  207. elseif rot == 7 then
  208. pitch, yaw = 0, -math.pi/2
  209. end
  210. end
  211. if def.drawtype == "signlike" then
  212. pitch = pitch - math.pi/2
  213. if rot == 0 then
  214. yaw = yaw + math.pi/2
  215. elseif rot == 1 then
  216. yaw = yaw - math.pi/2
  217. elseif rot == 6 then
  218. yaw = yaw - math.pi/2
  219. pitch = pitch + math.pi
  220. elseif rot == 7 then
  221. yaw = yaw + math.pi/2
  222. pitch = pitch + math.pi
  223. end
  224. elseif def.drawtype == "mesh" or def.drawtype == "normal" or def.drawtype == "nodebox" then
  225. if rot == 0 or rot == 1 then
  226. roll = roll + math.pi
  227. elseif rot == 6 or rot == 7 then
  228. if def.drawtype ~= "normal" then
  229. roll = roll - math.pi/2
  230. end
  231. else
  232. yaw = yaw + math.pi
  233. end
  234. end
  235. self.object:set_rotation({x=pitch, y=yaw, z=roll})
  236. elseif (def.drawtype == "mesh" and def.paramtype2 == "degrotate") then
  237. local p2 = (node.param2 - (def.place_param2 or 0)) % 240
  238. local yaw = (p2 / 240) * (math.pi * 2)
  239. self.object:set_yaw(yaw)
  240. elseif (def.drawtype == "mesh" and def.paramtype2 == "colordegrotate") then
  241. local p2 = (node.param2 % 32 - (def.place_param2 or 0) % 32) % 24
  242. local yaw = (p2 / 24) * (math.pi * 2)
  243. self.object:set_yaw(yaw)
  244. end
  245. end
  246. end,
  247. get_staticdata = function(self)
  248. local ds = {
  249. node = self.node,
  250. meta = self.meta,
  251. }
  252. return core.serialize(ds)
  253. end,
  254. on_activate = function(self, staticdata)
  255. self.object:set_armor_groups({immortal = 1})
  256. self.object:set_acceleration(vector.new(0, -gravity, 0))
  257. local ds = core.deserialize(staticdata)
  258. if ds and ds.node then
  259. self:set_node(ds.node, ds.meta)
  260. elseif ds then
  261. self:set_node(ds)
  262. elseif staticdata ~= "" then
  263. self:set_node({name = staticdata})
  264. end
  265. end,
  266. try_place = function(self, bcp, bcn)
  267. local bcd = core.registered_nodes[bcn.name]
  268. -- Add levels if dropped on same leveled node
  269. if bcd and bcd.paramtype2 == "leveled" and
  270. bcn.name == self.node.name then
  271. local addlevel = self.node.level
  272. if (addlevel or 0) <= 0 then
  273. addlevel = bcd.leveled
  274. end
  275. if core.add_node_level(bcp, addlevel) < addlevel then
  276. return true
  277. elseif bcd.buildable_to then
  278. -- Node level has already reached max, don't place anything
  279. return true
  280. end
  281. end
  282. -- Decide if we're replacing the node or placing on top
  283. -- This condition is very similar to the check in core.check_single_for_falling(p)
  284. local np = vector.copy(bcp)
  285. if bcd and bcd.buildable_to
  286. and -- Take "float" group into consideration:
  287. (
  288. -- Fall through non-liquids
  289. not self.floats or bcd.liquidtype == "none" or
  290. -- Only let sources fall through flowing liquids
  291. (self.floats and self.liquidtype ~= "none" and bcd.liquidtype ~= "source")
  292. ) then
  293. core.remove_node(bcp)
  294. else
  295. np.y = np.y + 1
  296. end
  297. -- Check what's here
  298. local n2 = core.get_node(np)
  299. local nd = core.registered_nodes[n2.name]
  300. -- If it's not air or liquid, remove node and replace it with
  301. -- it's drops
  302. if n2.name ~= "air" and (not nd or nd.liquidtype ~= "source") then
  303. if nd and nd.buildable_to == false then
  304. nd.on_dig(np, n2, nil)
  305. -- If it's still there, it might be protected
  306. if core.get_node(np).name == n2.name then
  307. return false
  308. end
  309. else
  310. core.remove_node(np)
  311. end
  312. end
  313. -- Create node
  314. local def = core.registered_nodes[self.node.name]
  315. if def then
  316. core.add_node(np, self.node)
  317. if self.meta then
  318. core.get_meta(np):from_table(self.meta)
  319. end
  320. if def.sounds and def.sounds.place then
  321. core.sound_play(def.sounds.place, {pos = np}, true)
  322. end
  323. end
  324. core.check_for_falling(np)
  325. return true
  326. end,
  327. on_step = function(self, dtime, moveresult)
  328. -- Fallback code since collision detection can't tell us
  329. -- about liquids (which do not collide)
  330. if self.floats then
  331. local pos = self.object:get_pos()
  332. local bcp = pos:offset(0, -0.7, 0):round()
  333. local bcn = core.get_node(bcp)
  334. local bcd = core.registered_nodes[bcn.name]
  335. if bcd and bcd.liquidtype ~= "none" then
  336. if self:try_place(bcp, bcn) then
  337. self.object:remove()
  338. return
  339. end
  340. end
  341. end
  342. assert(moveresult)
  343. if not moveresult.collides then
  344. return -- Nothing to do :)
  345. end
  346. local bcp, bcn
  347. local player_collision
  348. if moveresult.touching_ground then
  349. for _, info in ipairs(moveresult.collisions) do
  350. if info.type == "object" then
  351. if info.axis == "y" and info.object:is_player() then
  352. player_collision = info
  353. end
  354. elseif info.axis == "y" then
  355. bcp = info.node_pos
  356. bcn = core.get_node(bcp)
  357. break
  358. end
  359. end
  360. end
  361. if not bcp then
  362. -- We're colliding with something, but not the ground. Irrelevant to us.
  363. if player_collision then
  364. -- Continue falling through players by moving a little into
  365. -- their collision box
  366. -- TODO: this hack could be avoided in the future if objects
  367. -- could choose who to collide with
  368. local vel = self.object:get_velocity()
  369. self.object:set_velocity(vector.new(
  370. vel.x,
  371. player_collision.old_velocity.y,
  372. vel.z
  373. ))
  374. self.object:set_pos(self.object:get_pos():offset(0, -0.5, 0))
  375. end
  376. return
  377. elseif bcn.name == "ignore" then
  378. -- Delete on contact with ignore at world edges
  379. self.object:remove()
  380. return
  381. end
  382. local failure = false
  383. local pos = self.object:get_pos()
  384. local distance = vector.apply(vector.subtract(pos, bcp), math.abs)
  385. if distance.x >= 1 or distance.z >= 1 then
  386. -- We're colliding with some part of a node that's sticking out
  387. -- Since we don't want to visually teleport, drop as item
  388. failure = true
  389. elseif distance.y >= 2 then
  390. -- Doors consist of a hidden top node and a bottom node that is
  391. -- the actual door. Despite the top node being solid, the moveresult
  392. -- almost always indicates collision with the bottom node.
  393. -- Compensate for this by checking the top node
  394. bcp.y = bcp.y + 1
  395. bcn = core.get_node(bcp)
  396. local def = core.registered_nodes[bcn.name]
  397. if not (def and def.walkable) then
  398. failure = true -- This is unexpected, fail
  399. end
  400. end
  401. -- Try to actually place ourselves
  402. if not failure then
  403. failure = not self:try_place(bcp, bcn)
  404. end
  405. if failure then
  406. local drops = core.get_node_drops(self.node, "")
  407. for _, item in pairs(drops) do
  408. core.add_item(pos, item)
  409. end
  410. end
  411. self.object:remove()
  412. end
  413. })
  414. local function convert_to_falling_node(pos, node)
  415. local obj = core.add_entity(pos, "__builtin:falling_node")
  416. if not obj then
  417. return false
  418. end
  419. -- remember node level, the entities' set_node() uses this
  420. node.level = core.get_node_level(pos)
  421. local meta = core.get_meta(pos)
  422. local metatable = meta and meta:to_table() or {}
  423. local def = core.registered_nodes[node.name]
  424. if def and def.sounds and def.sounds.fall then
  425. core.sound_play(def.sounds.fall, {pos = pos}, true)
  426. end
  427. obj:get_luaentity():set_node(node, metatable)
  428. core.remove_node(pos)
  429. return true, obj
  430. end
  431. function core.spawn_falling_node(pos)
  432. local node = core.get_node(pos)
  433. if node.name == "air" or node.name == "ignore" then
  434. return false
  435. end
  436. return convert_to_falling_node(pos, node)
  437. end
  438. local function drop_attached_node(p)
  439. local n = core.get_node(p)
  440. local drops = core.get_node_drops(n, "")
  441. local def = core.registered_items[n.name]
  442. if def and def.preserve_metadata then
  443. local oldmeta = core.get_meta(p):to_table().fields
  444. -- Copy pos and node because the callback can modify them.
  445. local pos_copy = vector.copy(p)
  446. local node_copy = {name=n.name, param1=n.param1, param2=n.param2}
  447. local drop_stacks = {}
  448. for k, v in pairs(drops) do
  449. drop_stacks[k] = ItemStack(v)
  450. end
  451. drops = drop_stacks
  452. def.preserve_metadata(pos_copy, node_copy, oldmeta, drops)
  453. end
  454. if def and def.sounds and def.sounds.fall then
  455. core.sound_play(def.sounds.fall, {pos = p}, true)
  456. end
  457. core.remove_node(p)
  458. for _, item in pairs(drops) do
  459. local pos = {
  460. x = p.x + math.random()/2 - 0.25,
  461. y = p.y + math.random()/2 - 0.25,
  462. z = p.z + math.random()/2 - 0.25,
  463. }
  464. core.add_item(pos, item)
  465. end
  466. end
  467. function builtin_shared.check_attached_node(p, n, group_rating)
  468. local def = core.registered_nodes[n.name]
  469. local d = vector.zero()
  470. if group_rating == 3 then
  471. -- always attach to floor
  472. d.y = -1
  473. elseif group_rating == 4 then
  474. -- always attach to ceiling
  475. d.y = 1
  476. elseif group_rating == 2 then
  477. -- attach to facedir or 4dir direction
  478. if (def.paramtype2 == "facedir" or
  479. def.paramtype2 == "colorfacedir") then
  480. -- Attach to whatever facedir is "mounted to".
  481. -- For facedir, this is where tile no. 5 point at.
  482. -- The fallback vector here is in case 'facedir to dir' is nil due
  483. -- to voxelmanip placing a wallmounted node without resetting a
  484. -- pre-existing param2 value that is out-of-range for facedir.
  485. -- The fallback vector corresponds to param2 = 0.
  486. d = core.facedir_to_dir(n.param2) or vector.new(0, 0, 1)
  487. elseif (def.paramtype2 == "4dir" or
  488. def.paramtype2 == "color4dir") then
  489. -- Similar to facedir handling
  490. d = core.fourdir_to_dir(n.param2) or vector.new(0, 0, 1)
  491. end
  492. elseif def.paramtype2 == "wallmounted" or
  493. def.paramtype2 == "colorwallmounted" then
  494. -- Attach to whatever this node is "mounted to".
  495. -- This where tile no. 2 points at.
  496. -- The fallback vector here is used for the same reason as
  497. -- for facedir nodes.
  498. d = core.wallmounted_to_dir(n.param2) or vector.new(0, 1, 0)
  499. else
  500. d.y = -1
  501. end
  502. local p2 = vector.add(p, d)
  503. local nn = core.get_node(p2).name
  504. local def2 = core.registered_nodes[nn]
  505. if def2 and not def2.walkable then
  506. return false
  507. end
  508. return true
  509. end
  510. --
  511. -- Some common functions
  512. --
  513. function core.check_single_for_falling(p)
  514. local n = core.get_node(p)
  515. if core.get_item_group(n.name, "falling_node") ~= 0 then
  516. local p_bottom = vector.offset(p, 0, -1, 0)
  517. -- Only spawn falling node if node below is loaded
  518. local n_bottom = core.get_node_or_nil(p_bottom)
  519. local d_bottom = n_bottom and core.registered_nodes[n_bottom.name]
  520. if d_bottom then
  521. local same = n.name == n_bottom.name
  522. -- Let leveled nodes fall if it can merge with the bottom node
  523. if same and d_bottom.paramtype2 == "leveled" and
  524. core.get_node_level(p_bottom) <
  525. core.get_node_max_level(p_bottom) then
  526. local success, _ = convert_to_falling_node(p, n)
  527. return success
  528. end
  529. local d_falling = core.registered_nodes[n.name]
  530. local do_float = core.get_item_group(n.name, "float") > 0
  531. -- Otherwise only if the bottom node is considered "fall through"
  532. if not same and
  533. (not d_bottom.walkable or d_bottom.buildable_to)
  534. and -- Take "float" group into consideration:
  535. (
  536. -- Fall through non-liquids
  537. not do_float or d_bottom.liquidtype == "none" or
  538. -- Only let sources fall through flowing liquids
  539. (do_float and d_falling.liquidtype == "source" and d_bottom.liquidtype ~= "source")
  540. ) then
  541. local success, _ = convert_to_falling_node(p, n)
  542. return success
  543. end
  544. end
  545. end
  546. local an = core.get_item_group(n.name, "attached_node")
  547. if an ~= 0 then
  548. if not builtin_shared.check_attached_node(p, n, an) then
  549. drop_attached_node(p)
  550. return true
  551. end
  552. end
  553. return false
  554. end
  555. -- This table is specifically ordered.
  556. -- We don't walk diagonals, only our direct neighbors, and self.
  557. -- Down first as likely case, but always before self. The same with sides.
  558. -- Up must come last, so that things above self will also fall all at once.
  559. local check_for_falling_neighbors = {
  560. vector.new(-1, -1, 0),
  561. vector.new( 1, -1, 0),
  562. vector.new( 0, -1, -1),
  563. vector.new( 0, -1, 1),
  564. vector.new( 0, -1, 0),
  565. vector.new(-1, 0, 0),
  566. vector.new( 1, 0, 0),
  567. vector.new( 0, 0, 1),
  568. vector.new( 0, 0, -1),
  569. vector.new( 0, 0, 0),
  570. vector.new( 0, 1, 0),
  571. }
  572. function core.check_for_falling(p)
  573. -- Round p to prevent falling entities to get stuck.
  574. p = vector.round(p)
  575. -- We make a stack, and manually maintain size for performance.
  576. -- Stored in the stack, we will maintain tables with pos, and
  577. -- last neighbor visited. This way, when we get back to each
  578. -- node, we know which directions we have already walked, and
  579. -- which direction is the next to walk.
  580. local s = {}
  581. local n = 0
  582. -- The neighbor order we will visit from our table.
  583. local v = 1
  584. while true do
  585. -- Push current pos onto the stack.
  586. n = n + 1
  587. s[n] = {p = p, v = v}
  588. -- Select next node from neighbor list.
  589. p = vector.add(p, check_for_falling_neighbors[v])
  590. -- Now we check out the node. If it is in need of an update,
  591. -- it will let us know in the return value (true = updated).
  592. if not core.check_single_for_falling(p) then
  593. -- If we don't need to "recurse" (walk) to it then pop
  594. -- our previous pos off the stack and continue from there,
  595. -- with the v value we were at when we last were at that
  596. -- node
  597. repeat
  598. local pop = s[n]
  599. p = pop.p
  600. v = pop.v
  601. s[n] = nil
  602. n = n - 1
  603. -- If there's nothing left on the stack, and no
  604. -- more sides to walk to, we're done and can exit
  605. if n == 0 and v == 11 then
  606. return
  607. end
  608. until v < 11
  609. -- The next round walk the next neighbor in list.
  610. v = v + 1
  611. else
  612. -- If we did need to walk the neighbor, then
  613. -- start walking it from the walk order start (1),
  614. -- and not the order we just pushed up the stack.
  615. v = 1
  616. end
  617. end
  618. end
  619. --
  620. -- Global callbacks
  621. --
  622. local function on_placenode(p, node)
  623. core.check_for_falling(p)
  624. end
  625. core.register_on_placenode(on_placenode)
  626. local function on_dignode(p, node)
  627. core.check_for_falling(p)
  628. end
  629. core.register_on_dignode(on_dignode)
  630. local function on_punchnode(p, node)
  631. core.check_for_falling(p)
  632. end
  633. core.register_on_punchnode(on_punchnode)