Add crash guards against corrupt map data in mcl_core/functions.lua, update fire spread algorithm to limit spread while still burning things down, make burn spread dependent on area's humidity (more humid areas have less spread)

This commit is contained in:
teknomunk 2024-05-19 19:12:27 +00:00
parent 34b5c1705b
commit 09ab93a24d
2 changed files with 75 additions and 45 deletions

View File

@ -1239,6 +1239,10 @@ end
local function vinedecay_particles(pos, node) local function vinedecay_particles(pos, node)
local dir = minetest.wallmounted_to_dir(node.param2) local dir = minetest.wallmounted_to_dir(node.param2)
-- Don't crash if the map data got corrupted somehow
if not dir then return end
local relpos1, relpos2 local relpos1, relpos2
if dir.x < 0 then if dir.x < 0 then
relpos1 = { x = -0.45, y = -0.4, z = -0.5 } relpos1 = { x = -0.45, y = -0.4, z = -0.5 }
@ -1475,6 +1479,10 @@ Vines are considered “supported” if they face a walkable+solid block or “h
function mcl_core.check_vines_supported(pos, node) function mcl_core.check_vines_supported(pos, node)
local supported = false local supported = false
local dir = minetest.wallmounted_to_dir(node.param2) local dir = minetest.wallmounted_to_dir(node.param2)
-- Don't crash if the map data got corrupted somehow
if not dir then return false end
local pos1 = vector.add(pos, dir) local pos1 = vector.add(pos, dir)
local node_neighbor = minetest.get_node(pos1) local node_neighbor = minetest.get_node(pos1)
-- Check if vines are attached to a solid block. -- Check if vines are attached to a solid block.

View File

@ -44,8 +44,8 @@ shuffle_table(adjacents)
local function has_flammable(pos) local function has_flammable(pos)
for k,v in pairs(adjacents) do for k,v in pairs(adjacents) do
local p=vector.add(pos,v) local p=vector.add(pos,v)
local n=minetest.get_node_or_nil(p) local n=get_node_or_nil(p)
if n and minetest.get_item_group(n.name, "flammable") ~= 0 then if n and get_item_group(n.name, "flammable") ~= 0 then
return p return p
end end
end end
@ -63,8 +63,8 @@ local function get_adjacent_fire_age(pos)
local lowest_age = nil local lowest_age = nil
for k,v in pairs(all_adjacents) do for k,v in pairs(all_adjacents) do
local p = vector.add(pos, v) local p = vector.add(pos, v)
local node = minetest.get_node_or_nil(p) local node = get_node_or_nil(p)
if node and minetest.get_item_group(node.name, "fire") ~= 0 then if node and get_item_group(node.name, "fire") ~= 0 then
if not lowest_age or node.param2 < lowest_age then if not lowest_age or node.param2 < lowest_age then
lowest_age = node.param2 lowest_age = node.param2
end end
@ -112,23 +112,37 @@ else
eternal_fire_help = S("Eternal fire is a damaging block. Eternal fire can be extinguished by punches and nearby water blocks. Other than (normal) fire, eternal fire does not get extinguished on its own and also continues to burn under rain. Punching eternal fire is safe, but it hurts if you stand inside.") eternal_fire_help = S("Eternal fire is a damaging block. Eternal fire can be extinguished by punches and nearby water blocks. Other than (normal) fire, eternal fire does not get extinguished on its own and also continues to burn under rain. Punching eternal fire is safe, but it hurts if you stand inside.")
end end
local function spawn_fire(pos, age) -- exponential constant is such that at age=255, p=0.5%
local K1 = ( math.log(0.005) / math.log(10) ) / 255
local function spawn_fire(pos, age, force)
if not age then if not age then
minetest.log("warning","No age specified at "..debug.traceback())
-- Get adjacent age -- Get adjacent age
local adjacent_age = get_adjacent_fire_age(pos) local adjacent_age = get_adjacent_fire_age(pos)
-- Don't create new fire if we can't find adjacent fire from this position -- Don't create new fire if we can't find adjacent fire from this position
if not adjacent_age then return end if not adjacent_age then return end
age = math.random(1,5) + adjacent_age age = adjacent_age + math.ceil(minetest.get_humidity(pos)/10) + math.random(5)
end end
if age <= 1 then if age <= 1 then
minetest.log("warning","new flash point at "..vector.to_string(pos).." age="..tostring(age)..",backtrace = "..debug.traceback()) minetest.log("warning","new flash point at "..vector.to_string(pos).." age="..tostring(age)..",backtrace = "..debug.traceback())
end end
if age >= 255 then
age = 255
end
local node = get_node(pos)
local node_is_flammable = get_item_group(node.name, "flammable")
-- Limit fire spread -- Limit fire spread
local probability = math.pow(10,-1.176e-2 * age) -- exponential constant is such that at age=255, p=0.1% local probability_age = age
if math.random(65536)/65536 >= probability then if node_is_flammable then
probability_age = probability_age * 0.80
end
local probability = math.pow(10,K1 * probability_age)
if not force and math.random(65536)/65536 >= probability then
return return
end end
@ -399,32 +413,47 @@ if not fire_enabled then
else -- Fire enabled else -- Fire enabled
-- Extinguish parameters
local C2 = math.log(1/20) / math.log(10)
local K2 = -C2 / 255
-- Fire Spread -- Fire Spread
minetest.register_abm({ minetest.register_abm({
label = "Ignite flame", label = "Ignite flame",
nodenames ={"mcl_fire:fire","mcl_fire:eternal_fire"}, nodenames ={"mcl_fire:fire","mcl_fire:eternal_fire"},
interval = 7, interval = 7,
chance = 12, chance = 5,
catch_up = false, catch_up = false,
action = function(pos) action = function(pos)
local node = minetest.get_node(pos) local node = get_node(pos)
local age = node.param2 local age = node.param2
-- Always age the source fire
age = age + math.ceil(minetest.get_humidity(pos)/10) + math.random(5)
if age > 255 then age = 255 end
node.param2 = age
local p = get_ignitable(pos) local p = get_ignitable(pos)
if p then if p then
-- Spawn new fire with an age based on this node's age -- Spawn new fire with an age based on this node's age
spawn_fire(p, age+math.random(3,7)) spawn_fire(p, age + math.ceil(minetest.get_humidity(p)/10) + math.random(5))
shuffle_table(adjacents) shuffle_table(adjacents)
end end
-- Always age the source fire if node.name ~= "mcl_fire:eternal_fire" then
age = age + math.random(2,5) -- Randomly extinguish fires with increasing probability the older they are
node.param2 = age local extinguish_probability = math.pow(10,K2 * age + C2)
if age >= 255 then if math.random(65536)/65536 <= extinguish_probability then
node.name = "air" node.name = "air"
node.param2 = 0 node.param2 = 0
-- Extinguish fires not adjacent to flammable materials
elseif not has_flammable(pos) then
node.name = "air"
node.param2 = 0
end
end end
minetest.set_node(pos, node) set_node(pos, node)
end end
}) })
@ -439,26 +468,7 @@ else -- Fire enabled
action = function(pos) action = function(pos)
local p=get_ignitable_by_lava(pos) local p=get_ignitable_by_lava(pos)
if p then if p then
spawn_fire(p) spawn_fire(p, 0)
end
end,
})
minetest.register_abm({
label = "Remove fires",
nodenames = {"mcl_fire:fire"},
interval = 7,
chance = 3,
catch_up = false,
action = function(pos)
local p=has_flammable(pos)
if p then
local n=minetest.get_node_or_nil(p)
if n and minetest.get_item_group(n.name, "flammable") < 1 then
minetest.remove_node(pos)
end
else
minetest.remove_node(pos)
end end
end, end,
}) })
@ -477,15 +487,27 @@ else -- Fire enabled
return return
end end
local nn = minetest.get_node(p).name local node = get_node(p)
local def = minetest.registered_nodes[nn] local node_name = node.name
local fgroup = minetest.get_item_group(nn, "flammable") local def = minetest.registered_nodes[node_name]
local fgroup = minetest.get_item_group(node_name, "flammable")
if def and def._on_burn then if def and def._on_burn then
def._on_burn(p) def._on_burn(p)
elseif fgroup ~= -1 then elseif fgroup ~= -1 then
spawn_fire(p) local source_node = get_node(pos)
local age = source_node.param2
spawn_fire(p, age + math.ceil(minetest.get_humidity(p)/10) + math.random(5), true)
minetest.check_for_falling(p) minetest.check_for_falling(p)
if source_node.name == "mcl_fire:fire" then
-- Always age the source fire
age = age + math.ceil(minetest.get_humidity(pos)/10) + math.random(5)
if age > 255 then age = 255 end
source_node.param2 = age
set_node(pos, source_node)
end
end end
end end
}) })
@ -508,17 +530,17 @@ function mcl_fire.set_fire(pointed_thing, player, allow_on_fire)
return return
end end
local n_pointed = minetest.get_node(pointed_thing.under) local n_pointed = get_node(pointed_thing.under)
if allow_on_fire == false and get_item_group(n_pointed.name, "fire") ~= 0 then if allow_on_fire == false and get_item_group(n_pointed.name, "fire") ~= 0 then
return return
end end
local n_fire_pos = minetest.get_node(pointed_thing.above) local n_fire_pos = get_node(pointed_thing.above)
if n_fire_pos.name ~= "air" then if n_fire_pos.name ~= "air" then
return return
end end
local n_below = minetest.get_node(vector.offset(pointed_thing.above, 0, -1, 0)) local n_below = get_node(vector.offset(pointed_thing.above, 0, -1, 0))
if minetest.get_item_group(n_below.name, "water") ~= 0 then if minetest.get_item_group(n_below.name, "water") ~= 0 then
return return
end end