DML/modules/fireCtrl.lua
Christian Franz dff5faa06e Version 2.4.3
New FireCtrl,, lua2json bug work-around
2025-02-13 08:19:09 +01:00

410 lines
13 KiB
Lua

fireCtrl = {}
fireCtrl.version = "1.1.0"
fireCtrl.requiredLibs = {
"dcsCommon",
"cfxZones",
"airtank",
"inferno",
}
fireCtrl.heroes = {} -- dict by pname: score
fireCtrl.checkins = {} -- dict by pname: checked in if we persist
fireCtrl.roots = {}
--[[--
Version History
1.0.0 - Initial version (unreleased)
1.1.0 - Added attachTo:
- Added checkIn
- center name
- newFire sound
- bail out if not enough fires
- re-liting with sparks
- sparks attribute
- various sound attributes, default to action sound
- notifications attribute
- cleanup
- UI attribute
--]]--
function fireCtrl.checkinPlayer(pName, gName, uName, uType)
local theGroup = Group.getByName(gName)
if not theGroup then return end
gID = theGroup:getID()
local msg = ""
if not fireCtrl.checkins[pName] then
msg = "\nWelcome "
if fireCtrl.heroes[pName] then
msg = msg .. "back "
else
fireCtrl.heroes[pName] = 0
end
msg = msg .. "to " .. dcsCommon.getMapName() .. ", " .. pName .. ", your " .. uType .. " is ready.\nGood luck and godspeed!\n"
fireCtrl.checkins[pName] = timer.getTime()
else
msg = "\n" .. pName .. ", your " .. uType .. " is ready.\n"
end
if fireCtrl.checkIn then
trigger.action.outTextForGroup(gID, msg, 30, true)
trigger.action.outSoundForGroup(gID, fireCtrl.actionSound)
end
end
function fireCtrl:onEvent(theEvent)
-- catch birth events of helos
if not theEvent then return end
local theUnit = theEvent.initiator
if not theUnit then return end
if not theUnit.getPlayerName then return end
local pName = theUnit:getPlayerName()
if not pName then return end
-- we have a player unit
if not dcsCommon.unitIsOfLegalType(theUnit, airtank.types) then
if fireCtrl.verbose then
trigger.action.outText("fireCtrl: unit <" .. theUnit:getName() .. ">, type <" .. theUnit:getTypeName() .. "> not an airtank.", 30)
end
return
end
local uName = theUnit:getName()
local uType = theUnit:getTypeName()
local theGroup = theUnit:getGroup()
local gName = theGroup:getName()
if theEvent.id == 15 then -- birth
-- make sure this aircraft is legit
fireCtrl.installMenusForUnit(theUnit)
if fireCtrl.verbose then
trigger.action.outText("+++fCtl: new player airtank <" .. uName .. "> type <" .. uType .. "> for <" .. pName .. ">", 30)
end
fireCtrl.checkinPlayer(pName, gName, uName, uType)
return
end
end
function fireCtrl.installMenusForUnit(theUnit) -- assumes all unit types are weeded out
if not fireCtrl.UI then return end
-- if already exists, remove old
if not theUnit then return end
if not Unit.isExist(theUnit) then return end
local theGroup = theUnit:getGroup()
local uName = theUnit:getName()
local uType = theUnit:getTypeName()
local pName = theUnit:getPlayerName()
local gName = theGroup:getName()
local gID = theGroup:getID()
local pRoot = fireCtrl.roots[gName]
if pRoot then
missionCommands.removeItemForGroup(gID, pRoot)
pRoot = nil
end
-- handle main menu
local mainMenu = nil
if fireCtrl.mainMenu then
mainMenu = radioMenu.getMainMenuFor(fireCtrl.mainMenu)
end
-- now add fireCtrl menu
pRoot = missionCommands.addSubMenuForGroup(gID, fireCtrl.menuName, mainMenu)
fireCtrl.roots[gName] = pRoot -- save for later
local args = {gName, uName, gID, uType, pName}
-- menus:
-- report - all current open fires
-- action - scoreboard
-- arm release -- get ready to drop. auto-release when alt is below 30m
local m1 = missionCommands.addCommandForGroup(gID , "Fire Report" , pRoot, fireCtrl.redirectStatus, args)
local mx2 = missionCommands.addCommandForGroup(gID , "Action Report" , pRoot, fireCtrl.redirectAction, args)
end
--
-- comms
--
function fireCtrl.redirectStatus (args)
timer.scheduleFunction(fireCtrl.doStatus, args, timer.getTime() + 0.1)
end
function fireCtrl.redirectAction (args)
timer.scheduleFunction(fireCtrl.doAction, args, timer.getTime() + 0.1)
end
function fireCtrl.doStatus(args)
-- trigger.action.outText("status call", 30)
local gName = args[1]
local uName = args[2]
local gID = args[3]
local uType = args[4]
local pName = args[5]
local theUnit = Unit.getByName(uName)
if not theUnit then return end
local up = theUnit:getPoint()
up.y = 0
local msg = "\nFire emergencies requesting aerial support:\n"
local count = 0
for name, theZone in pairs(inferno.zones) do
if theZone.burning then
local p = theZone:getPoint()
local level = theZone.maxSpread
if level > 1000 or level < 0 then level = 1 end
count = count + 1
if count < 10 then msg = msg .. " " end
msg = msg .. count .. ". Type " .. level .. " "
if twn and towns then
local name, data, dist = twn.closestTownTo(p)
local mdist= dist * 0.539957
dist = math.floor(dist/100) / 10
mdist = math.floor(mdist/100) / 10
local bear = dcsCommon.compassPositionOfARelativeToB(p, data.p)
msg = msg .. dist .. "km/" .. mdist .."nm " .. bear .. " of " .. name
success = true
else
msg = msg .. "***TWN ERR***"
end
local b = dcsCommon.bearingInDegreesFromAtoB(up, p)
local d = dcsCommon.distFlat(up, p) * 0.000539957
d = math.floor(d * 10) / 10
msg = msg .. ", bearing " .. b .. ", " .. d .. "nm\n"
end
end
if count < 1 then
msg = msg .. "\n All is well, blue skies, no fires.\n"
end
msg = msg .. "\n"
trigger.action.outTextForGroup(gID, msg, 30)
trigger.action.outSoundForGroup(gID, fireCtrl.listSound)
end
function fireCtrl.doAction(args)
-- sort heroes by their points, and rank them
local h = {}
for name, num in pairs(fireCtrl.heroes) do
local ele = {}
ele.name = name
ele.num = num
ele.rank = fireCtrl.num2rank(num)
table.insert(h, ele)
end
-- table.sort(table, function (e1, e2) return e1.atr < e2.atr end )
table.sort(h, function (e1, e2) return e1.num > e2.num end)
-- now create the top twenty
local msg = "\nThe Book Of Embers recognizes:\n"
local count = 0
for idx, ele in pairs(h) do
count = count + 1
if count < 21 then
if count < 10 then msg = msg .. " " end
msg = msg .. count .. ". " .. ele.rank .. " " .. ele.name .. " (" .. ele.num .. ")\n"
end
end
if count < 1 then
msg = msg .. "\n *** The Book Is Empty ***\n"
end
trigger.action.outTextForGroup(gID, msg, 30)
trigger.action.outSoundForGroup(gID, fireCtrl.scoreSound)
end
function fireCtrl.num2rank(num)
if num < 10 then return "Probie" end
if num < 25 then return "Firefighter" end
if num < 50 then return "Elltee" end
if num < 100 then return "Chief" end
if num < 200 then return "Local Hero" end
if num < 1000 then return "Fire Hero of " .. dcsCommon.getMapName() .. "(" .. math.floor (num/200) .. ")" end
return "Avatar of Hephaestus"
end
--
-- update
--
function fireCtrl.pickUnlit()
local linearTable = dcsCommon.enumerateTable(inferno.zones)
local theZone
local tries = 0
repeat
theZone = dcsCommon.pickRandom(linearTable)
tries = tries + 1
until (not theZone.burning) or (tries > 100)
if tries > 100 then
trigger.action.outText("fireCtrl: no unlit zones available", 30)
return nil
end
return theZone
end
function fireCtrl.startFire()
local theZone = fireCtrl.pickUnlit()
if not theZone then return end
inferno.ignite(theZone)
if fireCtrl.verbose or theZone.verbose then
trigger.action.outText("+++fCtl: started fire in <" .. theZone.name .. ">", 30)
end
end
function fireCtrl.update()
timer.scheduleFunction(fireCtrl.update, {}, timer.getTime() + 1/fireCtrl.ups)
-- check the numbers of fires burning
local f = 0
local cells = 0
for idx, theZone in pairs(inferno.zones) do
if theZone.burning then f = f + 1 end
if theZone.maxSpread > 1000 then
cells = cells + #theZone.goodCells
else
cells = cells + theZone.maxSpread + 1
end
end
if f < fireCtrl.minFires then -- start 3!!!! fires
fireCtrl.startFire() -- start at least one fire
if fireCtrl.sparks > 1 then
for i=1, fireCtrl.sparks-1 do
if math.random(100) > 50 then fireCtrl.startFire() end
end
end
if fireCtrl.notifications then
trigger.action.outText("\nAll stations, " .. fireCtrl.centerName .. " One. Local fire dispatch have reported a large fire cell and are requesting aerial support. Please check in with " .. fireCtrl.centerName .. " for details.\n", 30)
trigger.action.outSound(fireCtrl.newFire)
end
end
end
--
-- callbacks
--
function fireCtrl.extCB(theZone)
local p = theZone:getPoint()
local name = "<" .. theZone.name .. ">"
if twn and towns then
name = twn.closestTownTo(p)
end
local msg = "\nInferno at " .. name .. " has been extinguished. Our thanks go to\n"
local heroes = theZone.heroes
local hasOne = false
if heroes then
local awarded = theZone.maxSpread
if awarded < 1 or awarded > 9999 then
awarded = 1
end
for name, count in pairs(heroes) do
hasOne = true
msg = msg .. " - " .. name .. " (" .. count .. " successful drops)\n"
-- award everyone points based on maxSprad
if not fireCtrl.heroes[name] then
fireCtrl.heroes[name] = awarded
else
fireCtrl.heroes[name] = fireCtrl.heroes[name] + awarded
end
end
end
if not hasOne then msg = msg .. "\n(no one)\n" end
trigger.action.outText(msg, 30)
trigger.action.outSound(fireCtrl.actionSound)
end
--
-- load / save (game data)
--
function fireCtrl.saveData()
local theData = {}
-- save current heroes. simple clone
local theHeroes = dcsCommon.clone(fireCtrl.heroes)
theData.theHeroes = theHeroes
return theData, fireCtrl.sharedData -- second val only if shared
end
function fireCtrl.loadData()
if not persistence then return end
local theData = persistence.getSavedDataForModule("fireCtrl", fireCtrl.sharedData)
if not theData then
if fireCtrl.verbose then
trigger.action.outText("+++fireCtrl: no save date received, skipping.", 30)
end
return
end
local theHeroes = theData.theHeroes
fireCtrl.heroes = theHeroes
end
--
-- start and config
--
function fireCtrl.readConfigZone()
fireCtrl.name = "fireCtrlConfig" -- make compatible with dml zones
local theZone = cfxZones.getZoneByName("fireCtrlConfig")
if not theZone then
theZone = cfxZones.createSimpleZone("fireCtrlConfig")
end
fireCtrl.verbose = theZone.verbose
fireCtrl.ups = theZone:getNumberFromZoneProperty("ups", 1/20)
fireCtrl.actionSound = theZone:getStringFromZoneProperty("actionSound", "none")
fireCtrl.listSound = theZone:getStringFromZoneProperty("listSound", fireCtrl.actionSound)
fireCtrl.scoreSound = theZone:getStringFromZoneProperty("scoreSound", fireCtrl.listSound)
fireCtrl.centerName = theZone:getStringFromZoneProperty("centerName", "Highperch")
fireCtrl.newFire = theZone:getStringFromZoneProperty("newFire", "firefight new fire alice.ogg")
fireCtrl.menuName = theZone:getStringFromZoneProperty("menuName", "Contact " .. fireCtrl.centerName)
fireCtrl.minFires = theZone:getNumberFromZoneProperty("minFires", 3)
fireCtrl.sparks = theZone:getNumberFromZoneProperty("sparks", 3)
fireCtrl.notifications = theZone:getBoolFromZoneProperty("notifications", true)
fireCtrl.UI = theZone:getBoolFromZoneProperty("UI", true)
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
fireCtrl.mainMenu = mainMenu
else
trigger.action.outText("+++fireCtrl: cannot find super menu <" .. attachTo .. ">", 30)
end
else
trigger.action.outText("+++fireCtrl: REQUIRES radioMenu to run before inferno. 'AttachTo:' ignored.", 30)
end
end
fireCtrl.checkIn = theZone:getBoolFromZoneProperty("checkIn", true)
-- shared data persistence interface
if theZone:hasProperty("sharedData") then
fireCtrl.sharedData = theZone:getStringFromZoneProperty("sharedData", "cfxNameMissing")
end
end
function fireCtrl.start()
if not dcsCommon.libCheck then
trigger.action.outText("cfx fireCtrl requires dcsCommon", 30)
return false
end
if not dcsCommon.libCheck("cfx fireCtrl", fireCtrl.requiredLibs) then
return false
end
-- read config
fireCtrl.readConfigZone()
if dcsCommon.getSizeOfTable(inferno.zones) < fireCtrl.minFires then
trigger.action.outText("+++fCtl: too few inferno zones (" .. fireCtrl.minFires .. " required). fireCtrl aborted.", 30)
return false
end
-- connect event handler
world.addEventHandler(fireCtrl)
-- install inferno extinguished CB
inferno.installExtinguishedCB(fireCtrl.extCB)
-- now load all save data
if persistence then
-- sign up for persistence
callbacks = {}
callbacks.persistData = fireCtrl.saveData
persistence.registerModule("fireCtrl", callbacks)
-- now load my data
fireCtrl.loadData()
end
-- start update
timer.scheduleFunction(fireCtrl.update, {}, timer.getTime() + 3)
-- say Hi!
trigger.action.outText("cf/x fireCtrl v" .. fireCtrl.version .. " started.", 30)
return true
end
if not fireCtrl.start() then
trigger.action.outText("fireCtrl failed to start up", 30)
fireCtrl = nil
end