init.lua 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. if not minetest.global_exists("instability") then instability = {} end
  2. instability.modpath = minetest.get_modpath("instability")
  3. -- Localize for speed.
  4. local allnodes = minetest.registered_nodes
  5. local stringf = string.find
  6. local ipairs = ipairs
  7. local mrandom = math.random
  8. local after = minetest.after
  9. local getn = minetest.get_node
  10. -- This helper function shall return TRUE if THIS NODE (nn) shall be considered
  11. -- to be supporting THE OTHER NODE (on). THE OTHER NODE (on) is the one being
  12. -- checked to see if it needs to fall.
  13. --
  14. -- This allows liquid nodes to support each other, but not anybody else.
  15. local function node_considered_supporting(nn, on)
  16. --minetest.chat_send_all(nn .. ', ' .. on)
  17. if nn == "air" then return false end
  18. if snow.is_snow(nn) then return false end
  19. -- 'ignore' must be supporting, to avoid accidents and problematic bugs.
  20. if nn == "ignore" then return true end
  21. if stringf(nn, "^throwing:") or
  22. stringf(nn, "^fire:") then
  23. -- Treating fences as non-supporting is apparently rather annoying.
  24. -- Fences are used for lampposts, etc.
  25. --stringf(nn, "^default:fence") or
  26. -- Ladders are self-supporting.
  27. --stringf(nn, "ladder")
  28. return false
  29. end
  30. -- Papyrus is self-supporting. Otherwise, digging 1 papyrus
  31. -- would cause nearby papyrus to fall, which looks bad.
  32. if nn == "default:papyrus" and on == "default:papyrus" then
  33. return true
  34. end
  35. -- Twisted vines are self-supporting.
  36. if stringf(nn, "default:tvine") and stringf(on, "default:tvine") then
  37. return true
  38. end
  39. local this_def = allnodes[nn]
  40. local other_def = allnodes[on]
  41. -- Unknown nodes always support, to prevent accidents.
  42. if not this_def or not other_def then
  43. --minetest.chat_send_all('deadbeaf')
  44. return true
  45. end
  46. if this_def.liquidtype == "source" then
  47. if other_def.liquidtype == "source" then
  48. --minetest.chat_send_all('liquid supporting')
  49. -- Liquid *sources* support other liquid sources.
  50. return true
  51. end
  52. -- Liquid does not support non-liquid nodes.
  53. return false
  54. elseif this_def.liquidtype == "flowing" then
  55. -- Flowing liquids never support nodes.
  56. return false
  57. end
  58. local this_groups = this_def.groups or {}
  59. local other_groups = other_def.groups or {}
  60. local this_drawtype = this_def.drawtype
  61. local this_paramtype = this_def.paramtype2
  62. -- Climbable supports adjacent climbable if they have the same drawtype.
  63. -- This works for ladders, climbable vines, etc.
  64. if this_def.climbable and other_def.climbable and this_def.drawtype == other_def.drawtype then
  65. return true
  66. end
  67. -- Walkable-falling nodes support plantlike nodes and attached nodes.
  68. if this_def.walkable and (this_groups.falling_node or 0) ~= 0 then
  69. if other_def.drawtype == "plantlike" or (other_groups.attached_node or 0) ~= 0 then
  70. return true
  71. elseif (other_groups.falling_node or 0) ~= 0 then
  72. -- Be more conservative: support other falling nodes. If the node needs to
  73. -- fall anyway, the falling node code will do it. This prevents us from
  74. -- having issues with lots of falling nodes packed together in a pile.
  75. return true
  76. end
  77. end
  78. -- Standing nodes and hanging nodes support each other.
  79. -- (utility.check_attached_node overrides this if it needs to on nodeupdate.)
  80. -- This is here mostly so we don't interfere with them.
  81. if (this_groups.hanging_node or 0) ~= 0 and (other_groups.hanging_node or 0) ~= 0 then
  82. return true
  83. end
  84. if (this_groups.standing_node or 0) ~= 0 and (other_groups.standing_node or 0) ~= 0 then
  85. return true
  86. end
  87. --minetest.chat_send_all('checking: ' .. nn .. ',' .. on)
  88. -- None of these drawtypes can support other nodes under normal circumstances.
  89. if this_drawtype == "airlike" or
  90. this_drawtype == "signlike" or
  91. this_drawtype == "torchlike" or
  92. this_drawtype == "raillike" or
  93. this_drawtype == "plantlike" or
  94. this_drawtype == "firelike" or
  95. (this_drawtype == "nodebox" and this_paramtype == "wallmounted") then
  96. return false
  97. end
  98. -- Attached nodes cannot support other nodes.
  99. if (this_groups.attached_node or 0) ~= 0 then
  100. return false
  101. end
  102. -- Falling nodes cannot support other nodes.
  103. if (this_groups.falling_node or 0) ~= 0 then
  104. return false
  105. end
  106. -- By default, node is considered supporting if we didn't handle it.
  107. --minetest.chat_send_all('supporting: ' .. nn .. ", supported: " .. on)
  108. return true
  109. end
  110. -- Determine if a single node is surrounded at 8 sides/corners by air.
  111. instability.is_singlenode = function(pos, axis)
  112. local nn = getn(pos).name
  113. -- Preserve the name of the node at the center.
  114. local cn = nn
  115. if nn == "air" or nn == "ignore" then return end
  116. -- The code tries to exit as early as possible if I can determine
  117. -- right away the node being checked is not a singlenode.
  118. if axis == "y" then
  119. local sides = {
  120. {x=pos.x-1, y=pos.y , z=pos.z },
  121. {x=pos.x+1, y=pos.y , z=pos.z },
  122. {x=pos.x , y=pos.y , z=pos.z-1},
  123. {x=pos.x , y=pos.y , z=pos.z+1},
  124. {x=pos.x-1, y=pos.y , z=pos.z-1},
  125. {x=pos.x+1, y=pos.y , z=pos.z-1},
  126. {x=pos.x-1, y=pos.y , z=pos.z+1},
  127. {x=pos.x+1, y=pos.y , z=pos.z+1},
  128. }
  129. for k, v in ipairs(sides) do
  130. local nn = getn(v).name
  131. if nn ~= "air" then
  132. if node_considered_supporting(nn, cn) then
  133. return false
  134. end
  135. end
  136. end
  137. return true -- Air all around.
  138. elseif axis == "x" then
  139. local sides = {
  140. {x=pos.x , y=pos.y-1, z=pos.z },
  141. {x=pos.x , y=pos.y+1, z=pos.z },
  142. {x=pos.x , y=pos.y , z=pos.z-1},
  143. {x=pos.x , y=pos.y , z=pos.z+1},
  144. {x=pos.x , y=pos.y-1, z=pos.z-1},
  145. {x=pos.x , y=pos.y+1, z=pos.z-1},
  146. {x=pos.x , y=pos.y-1, z=pos.z+1},
  147. {x=pos.x , y=pos.y+1, z=pos.z+1},
  148. }
  149. for k, v in ipairs(sides) do
  150. local nn = getn(v).name
  151. if nn ~= "air" then
  152. if node_considered_supporting(nn, cn) then
  153. return false
  154. end
  155. end
  156. end
  157. return true -- Air all around.
  158. elseif axis == "z" then
  159. local sides = {
  160. {x=pos.x-1, y=pos.y , z=pos.z },
  161. {x=pos.x+1, y=pos.y , z=pos.z },
  162. {x=pos.x , y=pos.y-1, z=pos.z },
  163. {x=pos.x , y=pos.y+1, z=pos.z },
  164. {x=pos.x-1, y=pos.y-1, z=pos.z },
  165. {x=pos.x+1, y=pos.y-1, z=pos.z },
  166. {x=pos.x-1, y=pos.y+1, z=pos.z },
  167. {x=pos.x+1, y=pos.y+1, z=pos.z },
  168. }
  169. for k, v in ipairs(sides) do
  170. local nn = getn(v).name
  171. if nn ~= "air" then
  172. if node_considered_supporting(nn, cn) then
  173. return false
  174. end
  175. end
  176. end
  177. return true -- Air all around.
  178. end
  179. end
  180. -- Determine if the node at a position is part of a 1x1 tower of nodes.
  181. -- (A node is part of a tower if the 8 locations around it are air.)
  182. instability.find_tower = function(args)
  183. -- Argument table:
  184. --[[
  185. {
  186. -- Position of first node to check.
  187. pos = {x=..., y=..., z=...},
  188. -- Direction along the axis (positive or negative).
  189. dir = "p|n",
  190. -- Axis to check along (allows checking for horizontal towers).
  191. axis = "x|y|z",
  192. -- Number of nodes to check.
  193. len = ...,
  194. }
  195. --]]
  196. -- Table of nodes which are part of the tower.
  197. -- This can be later used in a collapsing algorithm.
  198. local tower_nodes = {}
  199. local seek = function(pos, dir, axis, len)
  200. for i = 1, len, 1 do
  201. if not instability.is_singlenode(pos, axis) then
  202. return -- Early exit. Can't be a tower.
  203. end
  204. tower_nodes[#tower_nodes+1] = {x=pos.x, y=pos.y, z=pos.z}
  205. pos = vector.add(pos, dir)
  206. end
  207. end
  208. if args.axis == "y" then
  209. if args.dir == "p" then
  210. seek(args.pos, {x=0, y=1, z=0}, args.axis, args.len)
  211. elseif args.dir == "n" then
  212. seek(args.pos, {x=0, y=-1, z=0}, args.axis, args.len)
  213. end
  214. elseif args.axis == "x" then
  215. if args.dir == "p" then
  216. seek(args.pos, {x=1, y=0, z=0}, args.axis, args.len)
  217. elseif args.dir == "n" then
  218. seek(args.pos, {x=-1, y=0, z=0}, args.axis, args.len)
  219. end
  220. elseif args.axis == "z" then
  221. if args.dir == "p" then
  222. seek(args.pos, {x=0, y=0, z=1}, args.axis, args.len)
  223. elseif args.dir == "n" then
  224. seek(args.pos, {x=0, y=0, z=-1}, args.axis, args.len)
  225. end
  226. end
  227. -- Table of tower nodes. Will be empty if not a tower.
  228. return tower_nodes
  229. end
  230. local messages = {
  231. "Player <%s>'s unstable structure has collapsed!",
  232. "Player <%s> was slapped by physics.",
  233. "Player <%s>'s structure offended the physics rules!",
  234. "Player <%s> built something unstable.",
  235. "Player <%s> forgot that unstable builds tend to collapse, here.",
  236. "A structure built by <%s> just fell down.",
  237. }
  238. instability.check_tower_and_collapse = function(pos, axis, dir, pname)
  239. local tower = instability.find_tower({
  240. pos = pos,
  241. axis = axis,
  242. dir = dir,
  243. len = 20,
  244. })
  245. if #tower >= 4 then
  246. local chance = mrandom(4, 10)
  247. if #tower >= chance then -- Chance to fall increases with length.
  248. local dropped = false
  249. for k, v in ipairs(tower) do
  250. local node = getn(v)
  251. if not instability.node_exempt(node.name) then
  252. if sfn.drop_node(v) then
  253. dropped = true
  254. core.check_for_falling(v)
  255. end
  256. end
  257. end
  258. -- Write to chat only if something actually fell.
  259. if dropped then
  260. local dname = rename.gpn(pname)
  261. minetest.chat_send_all("# Server: " .. string.format(messages[mrandom(1, #messages)], dname))
  262. end
  263. end
  264. end
  265. end
  266. -- Public API function.
  267. instability.check_tower = function(pos, node, pref)
  268. if mrandom(1, 6) == 1 then -- Randomized checking.
  269. -- Some nodetypes should be exempt from checking.
  270. -- It is ok to build towers of these.
  271. if stringf(node.name, "scaffolding") then return end
  272. if stringf(node.name, "ladder") then return end
  273. if stringf(node.name, "rope") then return end
  274. if stringf(node.name, "chain") then return end
  275. -- Protector nodes shall be exempt!
  276. if stringf(node.name, "^protector:") then return end
  277. if stringf(node.name, "^city_block:") then return end
  278. -- Doors and beds are multi-node constructs.
  279. if stringf(node.name, "^doors:") then return end
  280. if stringf(node.name, "^beds:") then return end
  281. local pname = pref:get_player_name()
  282. instability.check_tower_and_collapse(pos, "x", "p", pname)
  283. instability.check_tower_and_collapse(pos, "x", "n", pname)
  284. instability.check_tower_and_collapse(pos, "y", "p", pname)
  285. instability.check_tower_and_collapse(pos, "y", "n", pname)
  286. instability.check_tower_and_collapse(pos, "z", "p", pname)
  287. instability.check_tower_and_collapse(pos, "z", "n", pname)
  288. end
  289. end
  290. instability.check_single_node = function(pos)
  291. if mrandom(1, 200) == 1 then
  292. local overhang = true
  293. local node = getn(pos)
  294. -- Do not cause exempt nodes to fall.
  295. if instability.node_exempt(node.name) then
  296. return
  297. end
  298. for i = 1, 3 do
  299. node = getn({x=pos.x, y=pos.y-i, z=pos.z})
  300. if node.name ~= "air" then
  301. -- If any node below the one just placed is not air, then it doesn't qualify as an overhang.
  302. overhang = false
  303. break
  304. end
  305. end
  306. if overhang then
  307. local p = {x=pos.x, y=pos.y, z=pos.z}
  308. node = getn(p)
  309. local delay = mrandom(2, 10)
  310. after(delay - 1.5, function()
  311. ambiance.sound_play("default_gravel_footstep", p, 1, 20)
  312. end)
  313. after(delay, function()
  314. local n2 = getn(p)
  315. if n2.name == node.name then
  316. sfn.drop_node(p)
  317. core.check_for_falling(p)
  318. end
  319. end)
  320. end
  321. end
  322. end
  323. function instability.node_exempt(name)
  324. if name == "air" or name == "ignore" or
  325. -- Because falling protectors would be very, very bad.
  326. stringf(name, "^protector:") or
  327. stringf(name, "^city_block:") or
  328. -- Waterlilies must be considered stable to avoid them getting destroyed.
  329. -- This is because water does not support, and they are floodable.
  330. name == "flowers:waterlily" or
  331. -- The ropeboxes themselves can still fall. The rope is automatically removed.
  332. -- We don't want to interfere with that.
  333. stringf(name, "^vines:rope") or
  334. -- Beds and doors are multi-node constructs. We must not split them.
  335. stringf(name, "^beds:") or
  336. stringf(name, "^doors:") or
  337. -- Falling fire is problematic.
  338. stringf(name, "^fire:") then
  339. return true
  340. end
  341. end
  342. -- Called everytime a node is dug.
  343. function instability.check_unsupported_around(p)
  344. local target_nodes = {
  345. {x=p.x, y=p.y+1, z=p.z},
  346. {x=p.x, y=p.y-1, z=p.z},
  347. {x=p.x+1, y=p.y, z=p.z},
  348. {x=p.x-1, y=p.y, z=p.z},
  349. {x=p.x, y=p.y, z=p.z+1},
  350. {x=p.x, y=p.y, z=p.z-1},
  351. }
  352. for j=1, #target_nodes, 1 do
  353. instability.check_unsupported_single(target_nodes[j])
  354. end
  355. end
  356. -- Called from the TNT code, for instance.
  357. function instability.check_unsupported_single(p)
  358. local n = getn(p)
  359. -- Preserve the name of the center node.
  360. local nn = n.name
  361. if not instability.node_exempt(nn) then
  362. local supporting_positions = {
  363. {x=p.x, y=p.y+1, z=p.z},
  364. {x=p.x, y=p.y-1, z=p.z},
  365. {x=p.x+1, y=p.y, z=p.z},
  366. {x=p.x-1, y=p.y, z=p.z},
  367. {x=p.x, y=p.y, z=p.z+1},
  368. {x=p.x, y=p.y, z=p.z-1},
  369. }
  370. local solid = false
  371. for i=1, #supporting_positions, 1 do
  372. local n = getn(supporting_positions[i])
  373. if node_considered_supporting(n.name, nn) then
  374. solid = true
  375. break -- No need for further checks.
  376. end
  377. end
  378. -- No supporting nodes!
  379. if not solid then
  380. if falling.could_fall_here(p) and sfn.drop_node(p) then
  381. core.check_for_falling(p)
  382. minetest.after(0.1, instability.check_unsupported_around, p)
  383. end
  384. end
  385. end
  386. end
  387. if not instability.run_once then
  388. local c = "instability:core"
  389. local f = instability.modpath .. "/init.lua"
  390. reload.register_file(c, f, false)
  391. instability.run_once = true
  392. end