forked from VoxeLibre/VoxeLibre
Merge pull request 'ITEMS/mcl_fire: fix laggy fire spread' (#1998) from fix-fire into master
Reviewed-on: MineClone2/MineClone2#1998
This commit is contained in:
commit
02361b2d01
|
@ -248,7 +248,7 @@ function mcl_beds.register_bed(name, def)
|
|||
paramtype2 = "facedir",
|
||||
is_ground_content = false,
|
||||
-- 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_blast_resistance = 1,
|
||||
sounds = def.sounds or default_sounds,
|
||||
|
|
|
@ -24,29 +24,33 @@ local get_connected_players = minetest.get_connected_players
|
|||
local vector = vector
|
||||
local math = math
|
||||
|
||||
-- inverse pyramid pattern above lava source, floor 1 of 2:
|
||||
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=
|
||||
{
|
||||
local adjacents = {
|
||||
{ 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 = 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 = {
|
||||
amount = 0.009,
|
||||
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.")
|
||||
end
|
||||
|
||||
local function fire_timer(pos)
|
||||
minetest.get_node_timer(pos):start(math.random(3, 7))
|
||||
end
|
||||
|
||||
local function spawn_fire(pos, 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})
|
||||
|
@ -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)
|
||||
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 = "",
|
||||
sounds = {},
|
||||
-- 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)
|
||||
end
|
||||
|
||||
fire_timer(pos)
|
||||
mcl_particles.spawn_smoke(pos, "fire", smoke_pdef)
|
||||
end,
|
||||
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)
|
||||
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)
|
||||
on_construct = function(pos)
|
||||
fire_timer(pos)
|
||||
|
||||
if has_mcl_portals then --Calling directly minetest.get_modpath consumes 4x more compute time
|
||||
mcl_portals.light_nether_portal(pos)
|
||||
end
|
||||
|
@ -400,6 +302,25 @@ if flame_sound then
|
|||
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
|
||||
|
@ -415,27 +336,13 @@ minetest.register_abm({
|
|||
chance = 1,
|
||||
catch_up = false,
|
||||
action = function(pos, node, active_object_count, active_object_count_wider)
|
||||
remove_node(pos)
|
||||
minetest.remove_node(pos)
|
||||
minetest.sound_play("fire_extinguish_flame",
|
||||
{pos = pos, max_hear_distance = 16, gain = 0.15}, true)
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
-- 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
|
||||
|
||||
-- Occasionally remove fire if fire disabled
|
||||
|
@ -446,67 +353,88 @@ if not fire_enabled then
|
|||
interval = 10,
|
||||
chance = 10,
|
||||
catch_up = false,
|
||||
action = remove_node,
|
||||
action = minetest.remove_node,
|
||||
})
|
||||
|
||||
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({
|
||||
label = "Ignite fire by lava",
|
||||
nodenames = {"group:lava"},
|
||||
neighbors = {"air"},
|
||||
nodenames = {"mcl_core:lava_source","mcl_nether:nether_lava_source"},
|
||||
neighbors = {"air","group:flammable"},
|
||||
interval = 7,
|
||||
chance = 3,
|
||||
catch_up = false,
|
||||
action = function(pos)
|
||||
local i, dir, target, node, i2, f
|
||||
i = math.random(1,9)
|
||||
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
|
||||
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"
|
||||
})
|
||||
local p=get_ignitable_by_lava(pos)
|
||||
if p then
|
||||
spawn_fire(p)
|
||||
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
|
||||
|
||||
-- Set pointed_thing on (normal) fire.
|
||||
|
|
Loading…
Reference in New Issue