init.lua 12 KB

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