Merge pull request 'ITEMS/mcl_fire: fix laggy fire spread' (#1998) from fix-fire into master

Reviewed-on: https://git.minetest.land/MineClone2/MineClone2/pulls/1998
This commit is contained in:
cora 2022-02-25 00:17:47 +00:00
commit 02361b2d01
2 changed files with 114 additions and 186 deletions

View File

@ -248,7 +248,7 @@ function mcl_beds.register_bed(name, def)
paramtype2 = "facedir", paramtype2 = "facedir",
is_ground_content = false, is_ground_content = false,
-- FIXME: Should be bouncy=66, but this would be a higher bounciness than slime blocks! -- FIXME: Should be bouncy=66, but this would be a higher bounciness than slime blocks!
groups = {handy = 1, flammable = 3, bed = 2, dig_by_piston=1, bouncy=33, fall_damage_add_percent=-50, not_in_creative_inventory = 1}, groups = {handy = 1, flammable = -1, bed = 2, dig_by_piston=1, bouncy=33, fall_damage_add_percent=-50, not_in_creative_inventory = 1},
_mcl_hardness = 0.2, _mcl_hardness = 0.2,
_mcl_blast_resistance = 1, _mcl_blast_resistance = 1,
sounds = def.sounds or default_sounds, sounds = def.sounds or default_sounds,

View File

@ -24,29 +24,33 @@ local get_connected_players = minetest.get_connected_players
local vector = vector local vector = vector
local math = math local math = math
-- inverse pyramid pattern above lava source, floor 1 of 2: local adjacents = {
local lava_fire=
{
{ x =-1, y = 1, z =-1},
{ x =-1, y = 1, z = 0},
{ x =-1, y = 1, z = 1},
{ x = 0, y = 1, z =-1},
{ x = 0, y = 1, z = 0},
{ x = 0, y = 1, z = 1},
{ x = 1, y = 1, z =-1},
{ x = 1, y = 1, z = 0},
{ x = 1, y = 1, z = 1}
}
local alldirs=
{
{ x =-1, y = 0, z = 0 }, { x =-1, y = 0, z = 0 },
{ x = 1, y = 0, z = 0 }, { x = 1, y = 0, z = 0 },
{ x = 0, y =-1, z = 0},
{ x = 0, y = 1, z = 0 }, { x = 0, y = 1, z = 0 },
{ x = 0, y =-1, z = 0 },
{ x = 0, y = 0, z =-1 }, { x = 0, y = 0, z =-1 },
{ x = 0, y = 0, z = 1} { x = 0, y = 0, z = 1 },
} }
local function shuffle_table(t)
for i = #t, 1, -1 do
local r = math.random(i)
t[i], t[r] = t[r], t[i]
end
end
shuffle_table(adjacents)
local function has_flammable(pos)
for k,v in pairs(adjacents) do
local p=vector.add(pos,v)
local n=minetest.get_node_or_nil(p)
if n and minetest.get_item_group(n.name, "flammable") ~= 0 then
return p
end
end
end
local smoke_pdef = { local smoke_pdef = {
amount = 0.009, amount = 0.009,
maxexptime = 4.0, maxexptime = 4.0,
@ -86,10 +90,6 @@ 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 fire_timer(pos)
minetest.get_node_timer(pos):start(math.random(3, 7))
end
local function spawn_fire(pos, age) local function spawn_fire(pos, age)
set_node(pos, {name="mcl_fire:fire", param2 = age}) set_node(pos, {name="mcl_fire:fire", param2 = age})
minetest.check_single_for_falling({x=pos.x, y=pos.y+1, z=pos.z}) minetest.check_single_for_falling({x=pos.x, y=pos.y+1, z=pos.z})
@ -124,82 +124,6 @@ minetest.register_node("mcl_fire:fire", {
minetest.sound_play("fire_extinguish_flame", {pos = pos, gain = 0.25, max_hear_distance = 16}, true) minetest.sound_play("fire_extinguish_flame", {pos = pos, gain = 0.25, max_hear_distance = 16}, true)
end end
end, end,
on_timer = function(pos)
local node = get_node(pos)
-- Age is a number from 0 to 15 and is increased every timer step.
-- "old" fire is more likely to be extinguished
local age = node.param2
local flammables = find_nodes_in_area({x=pos.x-1, y=pos.y-1, z=pos.z-1}, {x=pos.x+1, y=pos.y+4, z=pos.z+1}, {"group:flammable"})
local below = get_node({x=pos.x, y=pos.z-1, z=pos.z})
local below_is_flammable = get_item_group(below.name, "flammable") > 0
-- Extinguish fire
if (not fire_enabled) and (math.random(1,3) == 1) then
remove_node(pos)
return
end
if age == 15 and not below_is_flammable then
remove_node(pos)
return
elseif age > 3 and #flammables == 0 and not below_is_flammable and math.random(1,4) == 1 then
remove_node(pos)
return
end
local age_add = 1
-- If fire spread is disabled, we have to skip the "destructive" code
if (not fire_enabled) then
if age + age_add <= 15 then
node.param2 = age + age_add
set_node(pos, node)
end
-- Restart timer
fire_timer(pos)
return
end
-- Spawn fire to nearby flammable nodes
local is_next_to_flammable = find_node_near(pos, 2, {"group:flammable"}) ~= nil
if is_next_to_flammable and math.random(1,2) == 1 then
-- The fire we spawn copies the age of this fire.
-- This prevents fire from spreading infinitely far as the fire fire dies off
-- quicker the further it has spreaded.
local age_next = math.min(15, age + math.random(0, 1))
-- Select random type of fire spread
local burntype = math.random(1,2)
if burntype == 1 then
-- Spawn fire in air
local nodes = find_nodes_in_area({x=pos.x-1, y=pos.y-1, z=pos.z-1}, {x=pos.x+1, y=pos.y+4, z=pos.z+1}, {"air"})
while #nodes > 0 do
local r = math.random(1, #nodes)
if find_node_near(nodes[r], 1, {"group:flammable"}) then
spawn_fire(nodes[r], age_next)
break
else
table.remove(nodes, r)
end
end
else
-- Burn flammable block
local nodes = find_nodes_in_area({x=pos.x-1, y=pos.y-1, z=pos.z-1}, {x=pos.x+1, y=pos.y+4, z=pos.z+1}, {"group:flammable"})
if #nodes > 0 then
local r = math.random(1, #nodes)
local nn = get_node(nodes[r]).name
local ndef = minetest.registered_nodes[nn]
local fgroup = get_item_group(nn, "flammable")
if ndef and ndef._on_burn then
ndef._on_burn(nodes[r])
elseif fgroup ~= -1 then
spawn_fire(nodes[r], age_next)
end
end
end
end
-- Regular age increase
if age + age_add <= 15 then
node.param2 = age + age_add
set_node(pos, node)
end
-- Restart timer
fire_timer(pos)
end,
drop = "", drop = "",
sounds = {}, sounds = {},
-- Turn into eternal fire on special blocks, light Nether portal (if possible), start burning timer -- Turn into eternal fire on special blocks, light Nether portal (if possible), start burning timer
@ -216,7 +140,6 @@ minetest.register_node("mcl_fire:fire", {
mcl_portals.light_nether_portal(pos) mcl_portals.light_nether_portal(pos)
end end
fire_timer(pos)
mcl_particles.spawn_smoke(pos, "fire", smoke_pdef) mcl_particles.spawn_smoke(pos, "fire", smoke_pdef)
end, end,
on_destruct = function(pos) on_destruct = function(pos)
@ -254,29 +177,8 @@ minetest.register_node("mcl_fire:eternal_fire", {
minetest.sound_play("fire_extinguish_flame", {pos = pos, gain = 0.25, max_hear_distance = 16}, true) minetest.sound_play("fire_extinguish_flame", {pos = pos, gain = 0.25, max_hear_distance = 16}, true)
end end
end, end,
on_timer = function(pos)
if fire_enabled then
local airs = find_nodes_in_area({x=pos.x-1, y=pos.y-1, z=pos.z-1}, {x=pos.x+1, y=pos.y+4, z=pos.z+1}, {"air"})
while #airs > 0 do
local r = math.random(1, #airs)
if find_node_near(airs[r], 1, {"group:flammable"}) then
local node = get_node(airs[r])
local age = node.param2
local age_next = math.min(15, age + math.random(0, 1))
spawn_fire(airs[r], age_next)
break
else
table.remove(airs, r)
end
end
end
-- Restart timer
fire_timer(pos)
end,
-- Start burning timer and light Nether portal (if possible) -- Start burning timer and light Nether portal (if possible)
on_construct = function(pos) on_construct = function(pos)
fire_timer(pos)
if has_mcl_portals then --Calling directly minetest.get_modpath consumes 4x more compute time if has_mcl_portals then --Calling directly minetest.get_modpath consumes 4x more compute time
mcl_portals.light_nether_portal(pos) mcl_portals.light_nether_portal(pos)
end end
@ -400,6 +302,25 @@ if flame_sound then
end) end)
end end
-- [...]a fire that is not adjacent to any flammable block does not spread, even to another flammable block within the normal range.
-- https://minecraft.fandom.com/wiki/Fire#Spread
local function check_aircube(p1,p2)
local nds=minetest.find_nodes_in_area(p1,p2,{"air"})
shuffle_table(nds)
for k,v in pairs(nds) do
if has_flammable(v) then return v end
end
end
-- [...] a fire block can turn any air block that is adjacent to a flammable block into a fire block. This can happen at a distance of up to one block downward, one block sideways (including diagonals), and four blocks upward of the original fire block (not the block the fire is on/next to).
local function get_ignitable(pos)
return check_aircube(vector.add(pos,vector.new(-1,-1,-1)),vector.add(pos,vector.new(1,4,1)))
end
-- Fire spreads from a still lava block similarly: any air block one above and up to one block sideways (including diagonals) or two above and two blocks sideways (including diagonals) that is adjacent to a flammable block may be turned into a fire block.
local function get_ignitable_by_lava(pos)
return check_aircube(vector.add(pos,vector.new(-1,1,-1)),vector.add(pos,vector.new(1,1,1))) or check_aircube(vector.add(pos,vector.new(-2,2,-2)),vector.add(pos,vector.new(2,2,2))) or nil
end
-- --
-- ABMs -- ABMs
@ -415,27 +336,13 @@ minetest.register_abm({
chance = 1, chance = 1,
catch_up = false, catch_up = false,
action = function(pos, node, active_object_count, active_object_count_wider) action = function(pos, node, active_object_count, active_object_count_wider)
remove_node(pos) minetest.remove_node(pos)
minetest.sound_play("fire_extinguish_flame", minetest.sound_play("fire_extinguish_flame",
{pos = pos, max_hear_distance = 16, gain = 0.15}, true) {pos = pos, max_hear_distance = 16, gain = 0.15}, true)
end, end,
}) })
-- Enable the following ABMs according to 'enable fire' setting -- Enable the following ABMs according to 'enable fire' setting
local function has_flammable(pos)
local npos, node
for n, v in ipairs(alldirs) do
npos = vector.add(pos, v)
node = get_node_or_nil(npos)
if node and node.name and get_item_group(node.name, "flammable") ~= 0 then
return npos
end
end
return false
end
if not fire_enabled then if not fire_enabled then
-- Occasionally remove fire if fire disabled -- Occasionally remove fire if fire disabled
@ -446,67 +353,88 @@ if not fire_enabled then
interval = 10, interval = 10,
chance = 10, chance = 10,
catch_up = false, catch_up = false,
action = remove_node, action = minetest.remove_node,
}) })
else -- Fire enabled else -- Fire enabled
-- Set fire to air nodes -- Fire Spread
minetest.register_abm({
label = "Ignite flame",
nodenames ={"mcl_fire:fire","mcl_fire:eternal_fire"},
interval = 7,
chance = 12,
catch_up = false,
action = function(pos)
local p = get_ignitable(pos)
if p then
spawn_fire(p)
shuffle_table(adjacents)
end
end
})
--lava fire spread
minetest.register_abm({ minetest.register_abm({
label = "Ignite fire by lava", label = "Ignite fire by lava",
nodenames = {"group:lava"}, nodenames = {"mcl_core:lava_source","mcl_nether:nether_lava_source"},
neighbors = {"air"}, neighbors = {"air","group:flammable"},
interval = 7, interval = 7,
chance = 3, chance = 3,
catch_up = false, catch_up = false,
action = function(pos) action = function(pos)
local i, dir, target, node, i2, f local p=get_ignitable_by_lava(pos)
i = math.random(1,9) if p then
dir = lava_fire[i] spawn_fire(p)
target = {x=pos.x+dir.x, y=pos.y+dir.y, z=pos.z+dir.z}
node = get_node(target)
if not node or node.name ~= "air" then
i = ((i + math.random(0,7)) % 9) + 1
dir = lava_fire[i]
target = {x=pos.x+dir.x, y=pos.y+dir.y, z=pos.z+dir.z}
node = get_node(target)
if not node or node.name ~= "air" then
return
end
end
i2 = math.random(1,15)
if i2 < 10 then
local dir2, target2, node2
dir2 = lava_fire[i2]
target2 = {x=target.x+dir2.x, y=target.y+dir2.y, z=target.z+dir2.z}
node2 = get_node(target2)
if node2 and node2.name == "air" then
f = has_flammable(target2)
if f then
minetest.after(1, spawn_fire, {x=target2.x, y=target2.y, z=target2.z})
minetest.add_particle({
pos = vector.new({x=pos.x, y=pos.y+0.5, z=pos.z}),
velocity={x=f.x-pos.x, y=math.max(f.y-pos.y,0.7), z=f.z-pos.z},
expirationtime=1, size=1.5, collisiondetection=false,
glow=minetest.LIGHT_MAX, texture="mcl_particles_flame.png"
})
return
end
end
end
f = has_flammable(target)
if f then
minetest.after(1, spawn_fire, {x=target.x, y=target.y, z=target.z})
minetest.add_particle({
pos = vector.new({x=pos.x, y=pos.y+0.5, z=pos.z}),
velocity={x=f.x-pos.x, y=math.max(f.y-pos.y,0.25), z=f.z-pos.z},
expirationtime=1, size=1, collisiondetection=false,
glow=minetest.LIGHT_MAX, texture="mcl_particles_flame.png"
})
end end
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,
})
-- Remove flammable nodes around basic flame
minetest.register_abm({
label = "Remove flammable nodes",
nodenames = {"mcl_fire:fire","mcl_fire:eternal_fire"},
neighbors = {"group:flammable"},
interval = 5,
chance = 18,
catch_up = false,
action = function(pos)
local p = has_flammable(pos)
if not p then
return
end
local nn = minetest.get_node(p).name
local def = minetest.registered_nodes[nn]
local fgroup = minetest.get_item_group(nn, "flammable")
if def and def._on_burn then
def._on_burn(p)
elseif fgroup ~= -1 then
spawn_fire(p)
minetest.check_for_falling(p)
end
end
})
end end
-- Set pointed_thing on (normal) fire. -- Set pointed_thing on (normal) fire.