123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430 |
- instability = instability or {}
- instability.modpath = minetest.get_modpath("instability")
- -- Localize for speed.
- local allnodes = minetest.registered_nodes
- local stringf = string.find
- local ipairs = ipairs
- local mrandom = math.random
- local after = minetest.after
- local getn = minetest.get_node
- -- This helper function should determine whether a node is
- -- considered to be capable of supporting another node.
- -- First argument is the node to check. Second argument is the node it may or may not be supporting.
- -- This allows liquid nodes to support each other, but not anybody else.
- local function node_considered_supporting(nn, on)
- --minetest.chat_send_all(nn .. ', ' .. on)
- if nn == "air" then return false end
- if snow.is_snow(nn) then return false end
- -- 'ignore' must be supporting, to avoid accidents and problematic bugs.
- if nn == "ignore" then return true end
-
- if stringf(nn, "^throwing:") or
- stringf(nn, "^fire:") then
- -- Treating fences as non-supporting is apparently rather annoying.
- -- Fences are used for lampposts, etc.
- --stringf(nn, "^default:fence") or
- -- Ladders are self-supporting.
- --stringf(nn, "ladder")
- return false
- end
- if on then
- -- Ladders are self-supporting.
- if stringf(nn, "ladder") and stringf(on, "ladder") then
- return true
- end
- -- Papyrus is self-supporting. Otherwise, digging 1 papyrus
- -- would cause nearby papyrus to fall, which looks bad.
- if nn == "default:papyrus" and on == "default:papyrus" then
- return true
- end
- -- Twisted vines are self-supporting.
- if stringf(nn, "default:tvine") and stringf(on, "default:tvine") then
- return true
- end
- end
-
- local def = allnodes[nn]
- if def then
- if def.liquidtype == "source" or def.liquidtype == "flowing" then
- -- Did we get a name of a node we may or may not be supporting?
- if on then
- local d2 = allnodes[on]
- if d2 then
- if def.liquidtype == "source" and d2.liquidtype == "source" then
- -- Liquid *sources* support other liquids.
- return true
- end
- end
- end
- -- Liquid does not support.
- return false
- end
-
- local dt = def.drawtype
- local pt2 = def.paramtype2
- if dt == "airlike" or
- dt == "signlike" or
- dt == "torchlike" or
- dt == "raillike" or
- dt == "plantlike" or
- dt == "firelike" or
- (dt == "nodebox" and pt2 == "wallmounted") then
- return false
- end
-
- local groups = def.groups or {}
- if groups.attached_node then
- return false
- end
- end
-
- return true
- end
- -- Determine if a single node is surrounded at 8 sides/corners by air.
- instability.is_singlenode = function(pos, axis)
- local nn = getn(pos).name
- -- Preserve the name of the node at the center.
- local cn = nn
- if nn == "air" or nn == "ignore" then return end
-
- -- The code tries to exit as early as possible if I can determine
- -- right away the node being checked is not a singlenode.
-
- -- If the starting node isn't solid, don't bother checking.
- if not node_considered_supporting(nn) then
- return false
- end
-
- if axis == "y" then
- local sides = {
- {x=pos.x-1, y=pos.y , z=pos.z },
- {x=pos.x+1, y=pos.y , z=pos.z },
- {x=pos.x , y=pos.y , z=pos.z-1},
- {x=pos.x , y=pos.y , z=pos.z+1},
- {x=pos.x-1, y=pos.y , z=pos.z-1},
- {x=pos.x+1, y=pos.y , z=pos.z-1},
- {x=pos.x-1, y=pos.y , z=pos.z+1},
- {x=pos.x+1, y=pos.y , z=pos.z+1},
- }
- for k, v in ipairs(sides) do
- local nn = getn(v).name
- if nn ~= "air" then
- if node_considered_supporting(nn, cn) then
- return false
- end
- end
- end
- return true -- Air all around.
- elseif axis == "x" then
- local sides = {
- {x=pos.x , y=pos.y-1, z=pos.z },
- {x=pos.x , y=pos.y+1, z=pos.z },
- {x=pos.x , y=pos.y , z=pos.z-1},
- {x=pos.x , y=pos.y , z=pos.z+1},
- {x=pos.x , y=pos.y-1, z=pos.z-1},
- {x=pos.x , y=pos.y+1, z=pos.z-1},
- {x=pos.x , y=pos.y-1, z=pos.z+1},
- {x=pos.x , y=pos.y+1, z=pos.z+1},
- }
- for k, v in ipairs(sides) do
- local nn = getn(v).name
- if nn ~= "air" then
- if node_considered_supporting(nn, cn) then
- return false
- end
- end
- end
- return true -- Air all around.
- elseif axis == "z" then
- local sides = {
- {x=pos.x-1, y=pos.y , z=pos.z },
- {x=pos.x+1, y=pos.y , z=pos.z },
- {x=pos.x , y=pos.y-1, z=pos.z },
- {x=pos.x , y=pos.y+1, z=pos.z },
- {x=pos.x-1, y=pos.y-1, z=pos.z },
- {x=pos.x+1, y=pos.y-1, z=pos.z },
- {x=pos.x-1, y=pos.y+1, z=pos.z },
- {x=pos.x+1, y=pos.y+1, z=pos.z },
- }
- for k, v in ipairs(sides) do
- local nn = getn(v).name
- if nn ~= "air" then
- if node_considered_supporting(nn, cn) then
- return false
- end
- end
- end
- return true -- Air all around.
- end
- end
- -- Determine if the node at a position is part of a 1x1 tower of nodes.
- -- (A node is part of a tower if the 8 locations around it are air.)
- instability.find_tower = function(args)
- -- Argument table:
- --[[
- {
- -- Position of first node to check.
- pos = {x=..., y=..., z=...},
-
- -- Direction along the axis (positive or negative).
- dir = "p|n",
-
- -- Axis to check along (allows checking for horizontal towers).
- axis = "x|y|z",
-
- -- Number of nodes to check.
- len = ...,
- }
- --]]
-
- -- Table of nodes which are part of the tower.
- -- This can be later used in a collapsing algorithm.
- local tower_nodes = {}
- local seek = function(pos, dir, axis, len)
- for i = 1, len, 1 do
- if not instability.is_singlenode(pos, axis) then
- return -- Early exit. Can't be a tower.
- end
- tower_nodes[#tower_nodes+1] = {x=pos.x, y=pos.y, z=pos.z}
- pos = vector.add(pos, dir)
- end
- end
-
- if args.axis == "y" then
- if args.dir == "p" then
- seek(args.pos, {x=0, y=1, z=0}, args.axis, args.len)
- elseif args.dir == "n" then
- seek(args.pos, {x=0, y=-1, z=0}, args.axis, args.len)
- end
- elseif args.axis == "x" then
- if args.dir == "p" then
- seek(args.pos, {x=1, y=0, z=0}, args.axis, args.len)
- elseif args.dir == "n" then
- seek(args.pos, {x=-1, y=0, z=0}, args.axis, args.len)
- end
- elseif args.axis == "z" then
- if args.dir == "p" then
- seek(args.pos, {x=0, y=0, z=1}, args.axis, args.len)
- elseif args.dir == "n" then
- seek(args.pos, {x=0, y=0, z=-1}, args.axis, args.len)
- end
- end
-
- -- Table of tower nodes. Will be empty if not a tower.
- return tower_nodes
- end
- local messages = {
- "Player <%s>'s unstable structure has collapsed!",
- "Player <%s> was slapped by physics.",
- "Player <%s>'s structure offended the physics rules!",
- "Player <%s> built something unstable.",
- "Player <%s> forgot that unstable builds tend to collapse, here.",
- "A structure built by <%s> just fell down.",
- }
- instability.check_tower_and_collapse = function(pos, axis, dir, pname)
- local tower = instability.find_tower({
- pos = pos,
- axis = axis,
- dir = dir,
- len = 20,
- })
-
- if #tower >= 4 then
- local chance = mrandom(4, 10)
- if #tower >= chance then -- Chance to fall increases with length.
- local dropped = false
- for k, v in ipairs(tower) do
- local node = getn(v)
- if not instability.node_exempt(node.name) then
- if sfn.drop_node(v) then
- dropped = true
- core.check_for_falling(v)
- end
- end
- end
-
- -- Write to chat only if something actually fell.
- if dropped then
- local dname = rename.gpn(pname)
- minetest.chat_send_all("# Server: " .. string.format(messages[mrandom(1, #messages)], dname))
- end
- end
- end
- end
- -- Public API function.
- instability.check_tower = function(pos, node, pref)
- if mrandom(1, 6) == 1 then -- Randomized checking.
- -- Some nodetypes should be exempt from checking.
- -- It is ok to build towers of these.
- if stringf(node.name, "scaffolding") then return end
- if stringf(node.name, "ladder") then return end
- if stringf(node.name, "rope") then return end
- if stringf(node.name, "chain") then return end
-
- -- Protector nodes shall be exempt!
- if stringf(node.name, "^protector:") then return end
- if stringf(node.name, "^city_block:") then return end
-
- -- Doors and beds are multi-node constructs.
- if stringf(node.name, "^doors:") then return end
- if stringf(node.name, "^beds:") then return end
-
- local pname = pref:get_player_name()
-
- instability.check_tower_and_collapse(pos, "x", "p", pname)
- instability.check_tower_and_collapse(pos, "x", "n", pname)
-
- instability.check_tower_and_collapse(pos, "y", "p", pname)
- instability.check_tower_and_collapse(pos, "y", "n", pname)
-
- instability.check_tower_and_collapse(pos, "z", "p", pname)
- instability.check_tower_and_collapse(pos, "z", "n", pname)
- end
- end
- instability.check_single_node = function(pos)
- if mrandom(1, 200) == 1 then
- local overhang = true
- local node = getn(pos)
-
- -- Do not cause exempt nodes to fall.
- if instability.node_exempt(node.name) then
- return
- end
-
- for i = 1, 3 do
- node = getn({x=pos.x, y=pos.y-i, z=pos.z})
- if node.name ~= "air" then
- -- If any node below the one just placed is not air, then it doesn't qualify as an overhang.
- overhang = false
- break
- end
- end
- if overhang then
- local p = {x=pos.x, y=pos.y, z=pos.z}
- node = getn(p)
- local delay = mrandom(2, 10)
- after(delay - 1.5, function()
- ambiance.sound_play("default_gravel_footstep", p, 1, 20)
- end)
- after(delay, function()
- local n2 = getn(p)
- if n2.name == node.name then
- sfn.drop_node(p)
- core.check_for_falling(p)
- end
- end)
- end
- end
- end
- function instability.node_exempt(name)
- if name == "air" or name == "ignore" or
- -- Because falling protectors would be very, very bad.
- stringf(name, "^protector:") or
- stringf(name, "^city_block:") or
- -- Waterlilies must be considered stable to avoid them getting destroyed.
- -- This is because water does not support, and they are floodable.
- name == "flowers:waterlily" or
- -- The ropeboxes themselves can still fall. The rope is automatically removed.
- -- We don't want to interfere with that.
- stringf(name, "^vines:rope") or
- -- Beds and doors are multi-node constructs. We must not split them.
- stringf(name, "^beds:") or
- stringf(name, "^doors:") or
- -- Falling fire is problematic.
- stringf(name, "^fire:") then
- return true
- end
- end
- -- Called everytime a node is dug.
- function instability.check_unsupported_around(p)
- local target_nodes = {
- {x=p.x, y=p.y+1, z=p.z},
- {x=p.x, y=p.y-1, z=p.z},
- {x=p.x+1, y=p.y, z=p.z},
- {x=p.x-1, y=p.y, z=p.z},
- {x=p.x, y=p.y, z=p.z+1},
- {x=p.x, y=p.y, z=p.z-1},
- }
- for j=1, #target_nodes, 1 do
- instability.check_unsupported_single(target_nodes[j])
- end
- end
- -- Called from the TNT code, for instance.
- function instability.check_unsupported_single(p)
- local n = getn(p)
- -- Preserve the name of the center node.
- local nn = n.name
- if not instability.node_exempt(nn) then
- local supporting_positions = {
- {x=p.x, y=p.y+1, z=p.z},
- {x=p.x, y=p.y-1, z=p.z},
- {x=p.x+1, y=p.y, z=p.z},
- {x=p.x-1, y=p.y, z=p.z},
- {x=p.x, y=p.y, z=p.z+1},
- {x=p.x, y=p.y, z=p.z-1},
- }
- local solid = false
- for i=1, #supporting_positions, 1 do
- local n = getn(supporting_positions[i])
- if node_considered_supporting(n.name, nn) then
- solid = true
- break -- No need for further checks.
- end
- end
- -- No supporting nodes!
- if not solid then
- sfn.drop_node(p)
- core.check_for_falling(p)
- end
- end
- end
- if not instability.run_once then
- local c = "instability:core"
- local f = instability.modpath .. "/init.lua"
- reload.register_file(c, f, false)
-
- instability.run_once = true
- end
|