init.lua 12 KB

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