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:", "") 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