mirror of
https://github.com/weyne85/DML.git
synced 2025-10-29 16:57:49 +00:00
753 lines
25 KiB
Lua
753 lines
25 KiB
Lua
inferno = {}
|
|
inferno.version = "1.0.0"
|
|
inferno.requiredLibs = {
|
|
"dcsCommon",
|
|
"cfxZones",
|
|
}
|
|
--
|
|
-- Inferno models fires inside inferno zones. Fires can spread and
|
|
-- be extinguished by aircraft from the airtank module
|
|
-- (c) 2024 by Christian "cfrag" Franz
|
|
--
|
|
inferno.zones = {}
|
|
inferno.maxStages = 10 -- tied to fuel consumption, 1 burns small, maxStages at max size. A burning fire untended increases in stage by one each tick until it reaches maxStages
|
|
inferno.threshold = 4.9 -- when fire spreads to another field
|
|
inferno.fireExtinguishedCB = {} -- CB for other modules / scripts
|
|
|
|
--
|
|
-- CB
|
|
--
|
|
function inferno.installExtinguishedCB(theCB)
|
|
table.insert(inferno.fireExtinguishedCB, theCB)
|
|
end
|
|
|
|
function inferno.invokeFireExtinguishedCB(theZone)
|
|
for idx, cb in pairs(inferno.fireExtinguishedCB) do
|
|
cb(theZone, theZone.heroes)
|
|
end
|
|
end
|
|
|
|
--
|
|
-- Reading zones
|
|
--
|
|
function inferno.addZone(theZone)
|
|
inferno.zones[theZone.name] = theZone
|
|
end
|
|
|
|
function inferno.buildGrid(theZone)
|
|
-- default for circular zone
|
|
local radius = theZone.radius
|
|
local side = radius * 2
|
|
local p = theZone:getPoint()
|
|
local minx = p.x - radius
|
|
local minz = p.z - radius
|
|
local xside = side
|
|
local zside = side
|
|
local xradius = radius
|
|
local zradius = radius
|
|
if theZone.isPoly then
|
|
-- build the params for a (rectangular) zone from a
|
|
-- quad zone, makes area an AABB (axis-aligned bounding box)
|
|
minx = theZone.bounds.ll.x
|
|
minz = theZone.bounds.ll.z
|
|
xside = theZone.bounds.ur.x - theZone.bounds.ll.x
|
|
zside = theZone.bounds.ur.z - theZone.bounds.ll.z
|
|
end
|
|
local cellx = theZone.cellSize -- square cells assumed
|
|
local cellz = theZone.cellSize
|
|
local numX = math.floor(xside / cellx)
|
|
if numX < 1 then numX = 1 end
|
|
if numX > 100 then
|
|
trigger.action.outText("***inferno: limited x from <" .. numX .. "> to 100", 30)
|
|
numX = 100
|
|
end
|
|
|
|
local numZ = math.floor(zside / cellz)
|
|
if numX > 100 then
|
|
trigger.action.outText("***inferno: limited z from <" .. numZ .. "> to 100", 30)
|
|
numZ = 100
|
|
end
|
|
if numZ < 1 then numZ = 1 end
|
|
if theZone.verbose then
|
|
trigger.action.outText("infernal zone <" .. theZone.name .. ">: cellSize <" .. theZone.cellSize .. "> --> x <" .. numX .. ">, z <" .. numZ .. ">", 30)
|
|
end
|
|
local grid = {}
|
|
local goodCells = {}
|
|
-- Remember that in DCS
|
|
-- X is North/South, with positive X being North/South
|
|
-- and Z is East/West with positve Z being EAST
|
|
local fCount = 0
|
|
theZone.burning = false
|
|
for x=1, numX do -- "up/down"
|
|
grid[x] = {}
|
|
for z=1, numZ do -- "left/right"
|
|
local ele = {}
|
|
-- calculate center for each cell
|
|
local xc = minx + (x-1) * cellx + cellx/2
|
|
local zc = minz + (z-1) * cellz + cellz/2
|
|
local xf = xc
|
|
local zf = zc
|
|
|
|
local lp = {x=xc, y=zc}
|
|
local yc = land.getHeight(lp)
|
|
ele.center = {x=xc, y=yc, z=zc}
|
|
--[[--
|
|
if theZone.markCell then
|
|
dcsCommon.createStaticObjectForCoalitionAtLocation(0, ele.center, dcsCommon.uuid(theZone.name), "Black_Tyre_RF", 0, false)
|
|
|
|
dcsCommon.createStaticObjectForCoalitionAtLocation(0, {x=ele.center.x - cellx/2, y=0, z=ele.center.z - cellz/2}, dcsCommon.uuid(theZone.name), "Windsock", 0, false)
|
|
dcsCommon.createStaticObjectForCoalitionAtLocation(0, {x=ele.center.x + cellx/2, y=0, z=ele.center.z - cellz/2}, dcsCommon.uuid(theZone.name), "Windsock", 0, false)
|
|
dcsCommon.createStaticObjectForCoalitionAtLocation(0, {x=ele.center.x - cellx/2, y=0, z=ele.center.z + cellz/2}, dcsCommon.uuid(theZone.name), "Windsock", 0, false)
|
|
dcsCommon.createStaticObjectForCoalitionAtLocation(0, {x=ele.center.x + cellx/2, y=0, z=ele.center.z + cellz/2}, dcsCommon.uuid(theZone.name), "Windsock", 0, false)
|
|
end
|
|
--]]--
|
|
ele.fxpos = {x=xf, y=yc, z=zf}
|
|
ele.myType = land.getSurfaceType(lp) -- LAND=1, SHALLOW_WATER=2, WATER=3, ROAD=4, RUNWAY=5
|
|
-- we don not burn if a cell has shallow or deep water, or roads or runways
|
|
ele.inside = theZone:pointInZone(ele.center)
|
|
if theZone.freeBorder then
|
|
if x == 1 or x == numX then ele.inside = false end
|
|
if z == 1 or z == numZ then ele.inside = false end
|
|
end
|
|
ele.myStage = 0 -- not burning
|
|
if ele.inside and ele.myType == 1 then -- land only
|
|
-- create a position for the fire -- visual only
|
|
if theZone.stagger then
|
|
repeat
|
|
xf = xc + (0.5 - math.random()) * cellx
|
|
zf = zc + (0.5 - math.random()) * cellz
|
|
lp = {x=xf, y=zf}
|
|
until land.getSurfaceType(lp) == 1
|
|
ele.fxpos = {x=xf, y=yc, z=zf}
|
|
end
|
|
|
|
-- place a fire on the cell
|
|
if theZone.fullBlaze then
|
|
ele.fxname = dcsCommon.uuid(theZone.name)
|
|
trigger.action.effectSmokeBig(ele.fxpos, 4, 0.5 , ele.fxname)
|
|
ele.myStage = inferno.maxStages
|
|
ele.fxsize = 4
|
|
theZone.burning = true
|
|
end
|
|
local sparkable = {x=x, z=z}
|
|
table.insert(goodCells, sparkable)
|
|
fCount = fCount + 1
|
|
else
|
|
ele.inside = false -- optim: not in poly or burnable
|
|
end
|
|
ele.fuel = theZone.fuel -- use rnd?
|
|
ele.eternal = theZone.eternal -- unlimited fuel
|
|
grid[x][z] = ele
|
|
end -- for z
|
|
end -- for x
|
|
if theZone.verbose then
|
|
trigger.action.outText("inferno: zone <" .. theZone.name .. "> has <" .. fCount .. "> hot spots", 30)
|
|
end
|
|
if fCount < 1 then
|
|
trigger.action.outText("WARNING: <" .. theZone.name .. "> has no good burn cells!", 30)
|
|
end
|
|
theZone.numX = numX
|
|
theZone.numZ = numZ
|
|
theZone.minx = minx
|
|
theZone.minz = minz
|
|
theZone.xside = xside
|
|
theZone.grid = grid
|
|
theZone.goodCells = goodCells
|
|
|
|
-- find bestCell from goodCells closest to center
|
|
local bestdist = math.huge
|
|
local bestCell = nil
|
|
for idx, aCell in pairs(goodCells) do
|
|
local x = aCell.x
|
|
local z = aCell.z
|
|
local ele = grid[x][z]
|
|
local cp = ele.center
|
|
local d = dcsCommon.dist(cp, p)
|
|
if d < bestdist then
|
|
bestCell = aCell
|
|
bestdist = d
|
|
end
|
|
end
|
|
theZone.bestCell = bestCell
|
|
end
|
|
|
|
function inferno.readZone(theZone)
|
|
theZone.cellSize = theZone:getNumberFromZoneProperty("cellSize", inferno.cellSize)
|
|
-- FUEL: amount of fuel to burn PER CELL. when at zero, fire goes out
|
|
-- expansion: make it a random range
|
|
theZone.fuel = theZone:getNumberFromZoneProperty("fuel", 100)
|
|
theZone.rndLoc = theZone:getBoolFromZoneProperty("rndLoc", false)
|
|
theZone.freeBorder = theZone:getBoolFromZoneProperty("freeBorder", true) -- ring zone with non-burning zones
|
|
theZone.eternal = theZone:getBoolFromZoneProperty("eternal", true)
|
|
theZone.stagger = theZone:getBoolFromZoneProperty("stagger", true) -- randomize inside cell
|
|
theZone.fullBlaze = theZone:getBoolFromZoneProperty("fullBlaze", false )
|
|
theZone.canSpread = theZone:getBoolFromZoneProperty("canSpread", true)
|
|
theZone.maxSpread = theZone:getNumberFromZoneProperty("maxSpread", 999999)
|
|
theZone.impactSmoke = theZone:getBoolFromZoneProperty("impactSmoke", inferno.impactSmoke)
|
|
theZone.markCell = theZone:getBoolFromZoneProperty("markCell", false)
|
|
inferno.buildGrid(theZone)
|
|
theZone.heroes = {} -- remembers all who dropped into zone
|
|
theZone.onStart = theZone:getBoolFromZoneProperty("onStart", false)
|
|
if theZone:hasProperty("ignite?") then
|
|
theZone.ignite = theZone:getStringFromZoneProperty("ignite?", "none")
|
|
theZone.lastIgnite = trigger.misc.getUserFlag(theZone.ignite)
|
|
end
|
|
if theZone:hasProperty("douse?") then
|
|
theZone.douse = theZone:getStringFromZoneProperty("douse?", "none")
|
|
theZone.lastDouse = trigger.misc.getUserFlag(theZone.douse)
|
|
end
|
|
if theZone:hasProperty("extinguished!") then
|
|
theZone.extinguished = theZone:getStringFromZoneProperty("extinguished", "none")
|
|
end
|
|
|
|
end
|
|
|
|
--
|
|
-- API for water droppers
|
|
--
|
|
function inferno.surroundDelta(theZone, p, x, z)
|
|
if x < 1 then return math.huge end
|
|
if z < 1 then return math.huge end
|
|
if x > theZone.numX then return math.huge end
|
|
if z > theZone.numZ then return math.huge end
|
|
local ele = theZone.grid[x][z]
|
|
if not ele then return math.huge end
|
|
if not ele.inside then return math.huge end
|
|
if not ele.sparked then return math.huge end
|
|
if ele.myStage < 1 then return math.huge end
|
|
return dcsCommon.dist(p, ele.fxpos)
|
|
end
|
|
|
|
function inferno.waterInZone(theZone, p, amount)
|
|
-- water dropped (as point source) into a inferno zone.
|
|
-- find the cell that it was dropped in
|
|
local x = p.x - theZone.minx
|
|
local z = p.z - theZone.minz
|
|
local xc = math.floor(x / theZone.cellSize) + 1 -- square cells!
|
|
if xc > theZone.numX then return nil end -- xc = theZone.numX end -- was cut off,
|
|
local zc = math.floor(z / theZone.cellSize) + 1
|
|
if zc > theZone.numZ then return nil end -- zc = theZone.numZ end
|
|
local row = theZone.grid[xc]
|
|
if not row then
|
|
trigger.action.outText("inferno.waterinZone: cannot access row for xc = " .. xc .. ", x = " .. x .. ", numX = " .. theZone.numX .. " in " .. theZone.name, 30)
|
|
return "NIL row for xc " .. xc .. ", zc " .. zc .. " in " .. theZone.name
|
|
end
|
|
local ele = row[zc]
|
|
|
|
-- local ele = theZone.grid[xc][zc]
|
|
if not ele then
|
|
trigger.action.outText("Inferno: no ele for <" .. theZone.name .. ">: x<" .. x .. ">z<" .. z .. ">", 30)
|
|
trigger.action.outText("with xc = " .. xc .. ", numX 0 " .. theZone.numX .. ", zc = " .. zc .. ", numZ=" .. theZone.numZ, 30)
|
|
return "NIL ele for x" .. xc .. ",z" .. zc .. " in " .. theZone.name
|
|
end
|
|
|
|
-- empty ele pre-proccing:
|
|
-- if not burning, see if we find a better burning cell nearby
|
|
if (not ele.sparked) or ele.extinguished then -- not burning, note that we do NOT test inside here!
|
|
local hitDelta = math.sqrt(2) * theZone.cellSize -- dcsCommon.dist(p, ele.center) + 0.5 * theZone.cellSize -- give others a chance
|
|
local bestDelta = hitDelta
|
|
local ofx = 0
|
|
local ofz = 0
|
|
for dx = -1, 1 do
|
|
for dz = -1, 1 do
|
|
if dx == 0 and dz == 0 then -- skip this one
|
|
else
|
|
local newDelta = inferno.surroundDelta(theZone, p, xc + dx, zc + dz)
|
|
if newDelta < bestDelta then
|
|
bestDelta = newDelta
|
|
ofx = dx
|
|
ofz = dz
|
|
end
|
|
end
|
|
end
|
|
end
|
|
xc = xc + ofx
|
|
zc = zc + ofz
|
|
ele = theZone.grid[xc][zc]
|
|
-- else
|
|
-- trigger.action.outText("inferno dropper: NO ALIGNMENT, beds are burning.", 30)
|
|
end
|
|
if theZone.impactSmoke then
|
|
if inferno.verbose then
|
|
trigger.action.smoke(ele.center, 1) -- red is ele center
|
|
end
|
|
trigger.action.smoke(p, 4) -- blue is actual impact
|
|
end
|
|
|
|
-- inside?
|
|
if ele.inside then
|
|
-- force this cell's eternal to OFF, now it consumes fuel
|
|
ele.eternal = false -- from now on, this cell burns own fuel
|
|
|
|
if not ele.sparked then
|
|
-- not burning. remove all fuel, make it
|
|
-- extinguished so it won't catch fire in the future
|
|
ele.extinguished = true -- will now negatively contribute
|
|
ele.fuel = 0
|
|
return "Good peripheral delivery, will prevent spread."
|
|
end
|
|
|
|
-- calculate dispersal of water. the higher, the more dispersed
|
|
-- and less fuel on the ground is 'removed'
|
|
local dispAmount = amount -- currently no dispersal, full amount hits ground
|
|
ele.fuel = ele.fuel - dispAmount
|
|
|
|
-- we can restage fx to smaller fire and reset stage
|
|
-- so fire consumes less fuel ?
|
|
-- NYI, later.
|
|
return "Direct delivery into fire cell!"
|
|
end
|
|
|
|
-- not inside or a water tile
|
|
return nil
|
|
end
|
|
|
|
function inferno.waterDropped(p, amount, data) -- if returns non-nil, has hit a cell
|
|
-- p is (x, 0, z) of where the water hits the ground
|
|
for name, theZone in pairs(inferno.zones) do
|
|
if theZone:pointInZone(p) then
|
|
if inferno.verbose then
|
|
trigger.action.outText("inferno: INSIDE <" .. theZone.name .. ">", 30)
|
|
end
|
|
-- if available, remember and increase the number of drops in
|
|
-- zone for player
|
|
if data and data.pName then
|
|
if not theZone.heroes then theZone.heroes = {} end
|
|
if theZone.heroes[data.pName] then
|
|
theZone.heroes[data.pName] = theZone.heroes[data.pName] + 1
|
|
else
|
|
theZone.heroes[data.pName] = 1
|
|
end
|
|
|
|
end
|
|
return inferno.waterInZone(theZone, p, amount)
|
|
end
|
|
end
|
|
if inferno.impactSmoke then
|
|
-- mark the position with a blue smoke
|
|
trigger.action.smoke(p, 4)
|
|
end
|
|
if inferno.verbose then
|
|
trigger.action.outText("water drop outside any inferno zone", 30)
|
|
end
|
|
return nil
|
|
end
|
|
|
|
--
|
|
-- IGNITE & DOUSE
|
|
--
|
|
function inferno.sparkCell(theZone, x, z)
|
|
local ele = theZone.grid[x][z]
|
|
if not ele.inside then
|
|
if theZone.verbose then
|
|
trigger.action.outText("ele x<" .. x .. ">z<" .. z .. "> is outside, no spark!", 30)
|
|
end
|
|
return
|
|
false end
|
|
ele.fxname = dcsCommon.uuid(theZone.name)
|
|
trigger.action.effectSmokeBig(ele.fxpos, 1, 0.5 , ele.fxname)
|
|
ele.myStage = 1
|
|
ele.fxsize = 1
|
|
ele.sparked = true
|
|
return true
|
|
end
|
|
|
|
function inferno.ignite(theZone)
|
|
if theZone.burning then
|
|
-- later expansion: add more fires
|
|
-- will give error when fullblaze is set
|
|
trigger.action.outText("Zone <" .. theZone.name .. "> already burning", 30)
|
|
return
|
|
end
|
|
if theZone.verbose then
|
|
trigger.action.outText("igniting <" .. theZone.name .. ">", 30)
|
|
end
|
|
|
|
local midNum = math.floor((#theZone.goodCells + 1)/ 2)
|
|
if midNum < 1 then midNum = 1 end
|
|
local midCell = theZone.bestCell
|
|
if not midCell then midCell = theZone.goodCells[midNum] end
|
|
if theZone.rndLoc then midCell = dcsCommon.pickRandom(theZone.goodCells) end
|
|
local x = midCell.x
|
|
local z = midCell.z
|
|
|
|
if inferno.sparkCell(theZone, x, z) then
|
|
if theZone.verbose then
|
|
trigger.action.outText("Sparking cell x<" .. x .. ">z<" .. z .. "> for <" .. theZone.name .. ">", 30)
|
|
end
|
|
else
|
|
trigger.action.outText("Inferno: fire in <" .. theZone.name .. "> @ center x<" .. x .. ">z<" .. z .. "> didin't catch", 30)
|
|
end
|
|
theZone.hasSpread = 0 -- how many times we have spread
|
|
theZone.burning = true
|
|
end
|
|
|
|
function inferno.startFire(theZone)
|
|
if theZone.burning then
|
|
return
|
|
end
|
|
inferno.ignite(theZone)
|
|
end
|
|
|
|
function inferno.douseFire(theZone)
|
|
-- if not theZone.burning then
|
|
-- return
|
|
-- end
|
|
-- walk the grid, and kill all flames, set all eles
|
|
-- to end state
|
|
for x=1, theZone.numX do
|
|
for z=1, theZone.numZ do
|
|
local ele = theZone.grid[x][z]
|
|
if ele.inside then
|
|
if ele.fxname then
|
|
trigger.action.effectSmokeStop(ele.fxname)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
inferno.buildGrid(theZone) -- prep next fire in here
|
|
theZone.heroes = {}
|
|
theZone.burning = false
|
|
end
|
|
|
|
--
|
|
-- Fire Tick: progress/grow fire, burn fuel, expand conflagration etc.
|
|
--
|
|
function inferno.fireUpdate() -- update all burning fires
|
|
--[[--
|
|
every tick, we progress the fire status of all cells
|
|
fire stages (per cell):
|
|
< 1 : not burning, perhaps dying
|
|
0 : not burning, not smoking
|
|
-1..-5 : smoking. the closer to 0, less smoke
|
|
1..10 : burning, number = flame size, contib. heat and fuel consumption
|
|
--]]--
|
|
timer.scheduleFunction(inferno.fireUpdate, {}, timer.getTime() + inferno.fireTick) -- next time
|
|
for zName, theZone in pairs(inferno.zones) do
|
|
if theZone.fullBlaze then
|
|
-- do nothing, just testing layout
|
|
elseif theZone.burning then
|
|
inferno.burnOneTick(theZone) -- expand fuel, see if it spreads
|
|
end
|
|
end
|
|
end
|
|
|
|
function inferno.burnOneTick(theZone)
|
|
-- iterate all cells and see if the fire spreads
|
|
local isBurning = false
|
|
local grid = theZone.grid
|
|
local newStage = {} -- new states
|
|
local numX = theZone.numX
|
|
local numZ = theZone.numZ
|
|
-- pass 1:
|
|
-- calculate new stages
|
|
for x = 1, numX do -- up
|
|
newStage[x] = {}
|
|
for z = 1, numZ do
|
|
local ele = grid[x][z]
|
|
if ele.inside then
|
|
local stage = ele.myStage
|
|
-- we will only continue burning if we have fuel
|
|
if ele.extinguished then
|
|
-- we are drenched and can't re-ignite
|
|
newStage[x][z] = 0
|
|
elseif ele.fuel > 0 then -- we have fuel
|
|
if stage > 0 then -- it's already burning
|
|
if not ele.eternal then
|
|
ele.fuel = ele.fuel - stage -- consume fuel if burning and not eternal
|
|
if theZone.verbose then
|
|
trigger.action.outText(stage .. " fuel consumed. remain: "..ele.fuel, 30)
|
|
end
|
|
end
|
|
stage = stage + 1
|
|
if stage > inferno.maxStages then
|
|
stage = inferno.maxStages
|
|
end
|
|
newStage[x][z] = stage -- fire is growing
|
|
elseif stage < 0 then
|
|
-- fire is dying, can't be here if fuel > 0
|
|
newStage[x][z] = stage
|
|
else -- not burning. see if the surrounding sides are contributing
|
|
if theZone.canSpread and (theZone.hasSpread < theZone.maxSpread) then
|
|
local accu = 0
|
|
-- now do all surrounding 8 fields
|
|
-- NOTE: use wind direction to modify below if we use wind - NYI
|
|
accu = accu + inferno.contribute(x-1, z-1, theZone)
|
|
accu = accu + inferno.contribute(x, z-1, theZone)
|
|
accu = accu + inferno.contribute(x+1, z-1, theZone)
|
|
accu = accu + inferno.contribute(x-1, z, theZone)
|
|
accu = accu + inferno.contribute(x+1, z, theZone)
|
|
accu = accu + inferno.contribute(x-1, z+1, theZone)
|
|
accu = accu + inferno.contribute(x, z+1, theZone)
|
|
accu = accu + inferno.contribute(x+1, z+1, theZone)
|
|
accu = accu / 2 -- half intensity
|
|
-- 10% chance to spread when above threshold
|
|
if accu > inferno.threshold and math.random() < 0.1 then
|
|
stage = 1 -- start small fire
|
|
theZone.hasSpread = theZone.hasSpread + 1
|
|
end
|
|
end
|
|
newStage[x][z] = stage
|
|
end
|
|
else -- fuel is spent let flames die down if they exist
|
|
if stage == 0 then -- wasn't burning before
|
|
newStage[x][z] = 0
|
|
else
|
|
if stage > 0 then
|
|
newStage[x][z] = stage - 1
|
|
if newStage[x][z] == 0 then
|
|
newStage[x][z] = - 5
|
|
end
|
|
else
|
|
newStage[x][z] = stage + 1
|
|
end
|
|
end
|
|
end
|
|
else
|
|
newStage[x][z] = 0 -- outside, will always be 0
|
|
end
|
|
end -- for z
|
|
end -- for x
|
|
-- pass 2:
|
|
-- see what changed and handle accordingly
|
|
for x = 1, numX do -- up
|
|
for z = 1, numZ do
|
|
local ele = grid[x][z]
|
|
if ele.inside then
|
|
local stage = ele.myStage
|
|
|
|
local ns = newStage[x][z]
|
|
if not ns then ns = 0 end
|
|
if theZone.verbose and ele.sparked then
|
|
trigger.action.outText("x<" .. x .. ">z<" .. z .. "> - next stage is " .. ns, 30)
|
|
end
|
|
if ns ~= stage then
|
|
-- fire has changed: spread or dying down
|
|
if stage == 0 then -- fire has spread!
|
|
if theZone.verbose then
|
|
trigger.action.outText("Fire in <" .. theZone.name .. "> has spread to x<" .. x .. ">z<" .. z .. ">", 30)
|
|
end
|
|
ele.sparked = true
|
|
elseif ns == 0 then -- fire has died down fully
|
|
if theZone.verbose then
|
|
trigger.action.outText("Fire in <" .. theZone.name .. "> at x<" .. x .. ">z<" .. z .. "> has been extinguished", 30)
|
|
end
|
|
end
|
|
|
|
-- handle fire fx
|
|
-- determine fx number
|
|
local fx = 0
|
|
if stage > 0 then
|
|
fx = math.floor(stage / 2) -- 1..10--> 1..4
|
|
if fx < 1 then fx = 1 end
|
|
if fx > 4 then fx = 4 end
|
|
isBurning = true
|
|
elseif stage < 0 then
|
|
fx = 4-stage -- -5 .. -1 --> 6..10
|
|
if fx < 5 then fx = 5 end
|
|
if fx > 8 then fx = 8 end
|
|
isBurning = true -- keep as 'burning'
|
|
end
|
|
if fx ~= ele.fxsize then
|
|
if ele.fxname then
|
|
if theZone.verbose then
|
|
trigger.action.outText("removing old fx <" .. ele.fxsize .. "> [" .. ele.fxname .. "] for <" .. fx .. "> in <" .. theZone.name .. "> x<" .. x .. ">z<" .. z .. ">", 30)
|
|
end
|
|
-- remove old fx
|
|
trigger.action.effectSmokeStop(ele.fxname)
|
|
end
|
|
-- start new
|
|
if fx > 0 then
|
|
ele.fxname = dcsCommon.uuid(theZone.name)
|
|
trigger.action.effectSmokeBig(ele.fxpos, fx, 0.5 , ele.fxname)
|
|
else
|
|
if theZone.verbose then
|
|
trigger.action.outText("expiring <" .. theZone.name .. "> x<" .. x .. ">z<" .. z .. ">", 30)
|
|
end
|
|
end
|
|
ele.fxsize = fx
|
|
end
|
|
-- save new stage
|
|
ele.myStage = ns
|
|
else
|
|
if not ele.sparked then
|
|
-- not yet ignited, ignore
|
|
elseif ele.extinguished then
|
|
-- ignore, we are already off
|
|
elseif stage ~= 0 then
|
|
-- still burning bright
|
|
isBurning = true
|
|
else
|
|
-- remove last fx
|
|
trigger.action.effectSmokeStop(ele.fxname)
|
|
-- clear this zone or add debris now?
|
|
ele.extinguished = true -- now can't re-ignite
|
|
end
|
|
end -- if ns <> stage
|
|
end -- if inside
|
|
end -- for z
|
|
end -- for x
|
|
if not isBurning then
|
|
trigger.action.outText("inferno in <" .. theZone.name .. "> has been fully extinguished", 30)
|
|
theZone.burning = false
|
|
if theZone.extinguished then
|
|
theZone:pollFlag(theZone.extinguished, "inc")
|
|
end
|
|
inferno.invokeFireExtinguishedCB(theZone)
|
|
-- also fully douse this one, so we can restart it later
|
|
inferno.douseFire(theZone)
|
|
end
|
|
end
|
|
|
|
function inferno.contribute(x, z, theZone)
|
|
-- a cell starts burning if there is fuel
|
|
-- and the total contribution of all surrounding cells is
|
|
-- 10 or more, meaning that a 10 fire will ignite all surrounding
|
|
-- fields
|
|
-- bounds check
|
|
if x < 1 then return 0 end
|
|
if z < 1 then return 0 end
|
|
local numX = theZone.numX
|
|
if x > numX then return 0 end
|
|
local numZ = theZone.numZ
|
|
if z > numZ then return 0 end
|
|
local ele = theZone.grid[x][z]
|
|
if not ele.inside then return 0 end
|
|
if not ele.sparked then return 0 end -- not burning
|
|
if ele.extinguished then return -2 end -- water spill dampens
|
|
-- return stage that we are in if > 0
|
|
if ele.myStage >= 0 then
|
|
return ele.myStage -- mystage is positive int, "heat" 1..10
|
|
end
|
|
|
|
return 0
|
|
end
|
|
--
|
|
-- UPDATE
|
|
--
|
|
function inferno.update() -- for flag polling etc
|
|
timer.scheduleFunction(inferno.update, {}, timer.getTime() + 1/inferno.ups)
|
|
local fireNum = 0
|
|
for idx, theZone in pairs (inferno.zones) do
|
|
if theZone.ignite and
|
|
theZone:testZoneFlag(theZone.ignite, "change", "lastIgnite") then
|
|
inferno.startFire(theZone)
|
|
end
|
|
|
|
if theZone.douse and theZone:testZoneFlag(theZone.douse, "change", "lastDouse") then
|
|
inferno.douseFire(theZone)
|
|
end
|
|
if theZone.burning then
|
|
fireNum = fireNum + 1
|
|
end
|
|
end
|
|
if inferno.fireNum then
|
|
trigger.action.setUserFlag(inferno.fireNum, fireNum)
|
|
end
|
|
end
|
|
|
|
--
|
|
-- CONFIG & START
|
|
--
|
|
function inferno.readConfigZone()
|
|
inferno.name = "infernoConfig" -- make compatible with dml zones
|
|
local theZone = cfxZones.getZoneByName("infernoConfig")
|
|
if not theZone then
|
|
theZone = cfxZones.createSimpleZone("infernoConfig")
|
|
end
|
|
|
|
inferno.verbose = theZone.verbose
|
|
inferno.ups = theZone:getNumberFromZoneProperty("ups", 1)
|
|
inferno.fireTick = theZone:getNumberFromZoneProperty("fireTick", 10)
|
|
inferno.cellSize = theZone:getNumberFromZoneProperty("cellSize", 100)
|
|
--[[
|
|
inferno.menuName = theZone:getStringFromZoneProperty("menuName", "Firefighting")
|
|
inferno.impactSmoke = theZone:getBoolFromZoneProperty("impactSmoke", false)
|
|
|
|
if theZone:hasProperty("attachTo:") then
|
|
local attachTo = theZone:getStringFromZoneProperty("attachTo:", "<none>")
|
|
if radioMenu then -- requires optional radio menu to have loaded
|
|
local mainMenu = radioMenu.mainMenus[attachTo]
|
|
if mainMenu then
|
|
inferno.mainMenu = mainMenu
|
|
else
|
|
trigger.action.outText("+++inferno: cannot find super menu <" .. attachTo .. ">", 30)
|
|
end
|
|
else
|
|
trigger.action.outText("+++inferno: REQUIRES radioMenu to run before inferno. 'AttachTo:' ignored.", 30)
|
|
end
|
|
end
|
|
--]]--
|
|
if theZone:hasProperty("fire#") then
|
|
inferno.fireNum = theZone:getStringFromZoneProperty("fire#", "none")
|
|
end
|
|
|
|
end
|
|
|
|
function inferno.start()
|
|
if not dcsCommon.libCheck then
|
|
trigger.action.outText("cfx inferno requires dcsCommon", 30)
|
|
return false
|
|
end
|
|
if not dcsCommon.libCheck("cfx inferno", inferno.requiredLibs) then
|
|
return false
|
|
end
|
|
|
|
-- read config
|
|
inferno.readConfigZone()
|
|
|
|
-- process inferno Zones
|
|
local attrZones = cfxZones.getZonesWithAttributeNamed("inferno")
|
|
for k, aZone in pairs(attrZones) do
|
|
inferno.readZone(aZone) -- process attributes
|
|
inferno.addZone(aZone) -- add to list
|
|
end
|
|
|
|
-- start update (DML)
|
|
timer.scheduleFunction(inferno.update, {}, timer.getTime() + 1/inferno.ups)
|
|
|
|
-- start fire tick update
|
|
timer.scheduleFunction(inferno.fireUpdate, {}, timer.getTime() + inferno.fireTick)
|
|
|
|
-- start all zones that have onstart
|
|
for gName, theZone in pairs(inferno.zones) do
|
|
if theZone.onStart then
|
|
inferno.ignite(theZone)
|
|
end
|
|
end
|
|
-- say Hi!
|
|
trigger.action.outText("cf/x inferno v" .. inferno.version .. " started.", 30)
|
|
return true
|
|
end
|
|
|
|
if not inferno.start() then
|
|
trigger.action.outText("inferno failed to start up")
|
|
inferno = nil
|
|
end
|
|
|
|
--[[--
|
|
"ele" structure in grid
|
|
- fuel amount of fuel to burn. when < 0 the fire starves over the next cycles. By dumping water helicopters/planes reduce amount of available fuel
|
|
- extinguished if true, can't re-ignite
|
|
- myStage -- FSM for flames: >0 == burning, consumes fuel, <0 is starved of fuel and get smaller each tick
|
|
- fxname to reference flame fx
|
|
- eternal if true does not consume fuel. goes false when the first drop of water enters the cell from players
|
|
|
|
- center point of center
|
|
- fxpos - point in cell that has the fx
|
|
- mytype land type. only 1 burns
|
|
- inside is it inside the zone and can burn? true if so. used to make cells unburnable
|
|
- sparked if false this isn't burning
|
|
|
|
|
|
to do:
|
|
OK - callback for extinguis
|
|
OK - remember who contributes dropped inside successful and then receives ack when zone fully doused
|
|
|
|
Possible enhancements
|
|
- random range fuel
|
|
- wind for contribute (can precalc into table)
|
|
- boom in ignite
|
|
- clear after burn out
|
|
- leave debris after burn out, mabe place in ignite
|
|
--]]-- |