* Initial Methods for SpearheadAPI

* Added Spearhead API

* Added Spearhead API

* fixed compile for api

* Added notes for API in beta

* Added notes for API in beta

* Added Menu to all doc pages

* Added Menu to all doc pages

* added it for real

* added it for real

* fixed develop

* fixed develop

* fixed develop

* wip

* wip

* updated

* work in progress

* work in progress

* added empty docs page

* wip

* removed SAR files from persistence branch

* Added Blue Sams as seperate class

* added initial persistance

* added initial persistance

* added initial persistance

* added initial persistance

* added initial persistance

* added menu to persistence markdown

* added menu to persistence markdown

* added menu to persistence markdown

* docs update

* wip

* wip

* Refactor of Stage classes.

* wip

* wip

* wip

* reworked bluesam and stagebase

* Added waiting stage functionality

* updated docs

* wip

* wip

* wip

* updated

* wip

* wip

* wip

* refactored without issues, needs testing

---------

Co-authored-by: Dutchie <54616262+dutchie032@users.noreply.github.com>
Co-authored-by: dutchie032 <dutchie032>
Co-authored-by: ex61wi <tim.rorije@ing.com>
This commit is contained in:
2025-04-22 14:39:22 +02:00
committed by GitHub
co-authored by Dutchie dutchie032 <dutchie032> ex61wi
parent 9c6a5ed217
commit ff392e0e7e
37 changed files with 4494 additions and 1732 deletions
+74
View File
@@ -0,0 +1,74 @@
---@class AirGroup
---@field groupName string
---@field groupState GroupState
local AirGroup = {}
---@alias GroupState
---| "UnSpawned"
---| "ReadOnTheRamp
---| "InTransit"
---| "OnStation"
---| "RtbInTen"
---| "Rtb"
---| "Dead"
---| "Rearming"
---@alias AirGroupType
---| "CAP"
---| "CAS"
---| "SEAD"
---| "INTERCEPT"
---| ""
---@class GroupNameData
---@field type AirGroupType
---@field isBackup boolean
---@field zonesConfig table<string, string>
local function parseGroupName(groupName)
local split_string = Spearhead.Util.split_string(groupName, "_")
local partCount = Spearhead.Util.tableLength(split_string)
if partCount >= 3 then
---@type boolean
local isBackup = false
do -- config
end
else
Spearhead.AddMissionEditorWarning("CAP Group with name: " .. groupName .. "should have at least 3 parts, but has " .. partCount)
return nil
end
end
---comment
---@generic T: AirGroup
---@param o T
---@param groupName string
---@return T
function AirGroup.New(o, groupName)
AirGroup.__index = AirGroup
local o = o or {}
local self = setmetatable(o, AirGroup)
self.groupName = groupName
self.groupState = "UnSpawned"
return self
end
+1 -1
View File
@@ -48,7 +48,7 @@ function CapConfig:new()
local deathDelay = tonumber(SpearheadConfig.CapConfig.deathDelay) or 1800
---@return number
o.getDeathDelay = function(self) return deathDelay end
o.logLevel = Spearhead.LoggerTemplate.LogLevelOptions.INFO
o.logLevel = "INFO"
return o;
end
+25 -31
View File
@@ -1,44 +1,38 @@
--- @class StageConfig
--- @field isEnabled boolean
--- @field isDrawStagesEnabled boolean
--- @field isAutoStages boolean
--- @field startingStage integer
--- @field maxMissionsPerStage integer
--- @field logLevel LogLevel
local StageConfig = {};
---comment
---@return StageConfig
function StageConfig:new()
local o = {}
setmetatable(o, { __index = self })
if SpearheadConfig == nil then SpearheadConfig = {} end
if SpearheadConfig.StageConfig == nil then SpearheadConfig.StageConfig = {} end
local enabled = SpearheadConfig.StageConfig.enabled
if enabled == nil then enabled = true end
---@return boolean
o.isEnabled = function(self) return enabled == true end
---@type StageConfig
local o = {
isEnabled = SpearheadConfig.StageConfig.enabled or true,
isDrawStagesEnabled = SpearheadConfig.StageConfig.drawStages or true,
isAutoStages = SpearheadConfig.StageConfig.autoStages or true,
startingStage = SpearheadConfig.StageConfig.startingStage or 1,
maxMissionsPerStage = SpearheadConfig.StageConfig.maxMissionStage or 10,
logLevel = "INFO"
}
local drawStages = SpearheadConfig.StageConfig.drawStages
if drawStages == nil then drawStages = true end
---@return boolean
o.isDrawStagesEnabled = function(self) return drawStages == true end
local autoStages = SpearheadConfig.StageConfig.autoStages or true
if autoStages == nil then autoStages = true end
---@return boolean
o.isAutoStages = function(self) return autoStages end
local maxMissionsPerStage = SpearheadConfig.StageConfig.maxMissionStage
o.getMaxMissionsPerStage = function(self) return maxMissionsPerStage end
o.logLevel = Spearhead.LoggerTemplate.LogLevelOptions.INFO
local startingStage = SpearheadConfig.StageConfig.startingStage or 1
o.getStartingStage = function(self) return startingStage end
o.toString = function()
return Spearhead.Util.toString({
maxMissionsPerStage = maxMissionsPerStage,
enabled = enabled,
drawStages = drawStages,
autoStages = autoStages
})
if SpearheadConfig.StageConfig.debugEnabled == true then
o.logLevel = "DEBUG"
end
setmetatable(o, { __index = self })
return o;
end
+39
View File
@@ -0,0 +1,39 @@
do -- mission aliases
--- @alias MissionPriority
--- | "primary"
--- | "secondary"
--- @alias missionType
--- | "nil"
--- | "STRIKE"
--- | "BAI"
--- | "DEAD"
--- | "SAM"
--- @alias MissionState
--- | "NEW"
--- | "ACTIVE"
--- | "COMPLETED"
--- @alias LogLevel
--- | "DEBUG"
--- | "INFO"
--- | "WARN"
--- | "ERROR"
--- | "NONE"
---@class Array<T>: { [integer]: T }
--- @class Position
--- @field x number x position (Top-Down on Map)
--- @field y number y altitude
--- @field z number z position (Left-Right on Map)
end
+1 -1
View File
@@ -6,7 +6,7 @@ local fleetGroups = {}
GlobalFleetManager.start = function(database)
local logger = Spearhead.LoggerTemplate:new("CARRIERFLEET", Spearhead.LoggerTemplate.LogLevelOptions.INFO)
local logger = Spearhead.LoggerTemplate:new("CARRIERFLEET", "INFO")
local all_groups = Spearhead.DcsUtil.getAllGroupNames()
for _, groupName in pairs(all_groups) do
+204
View File
@@ -0,0 +1,204 @@
local Persistence = {}
do
---@class PersistentData
---@field dead_units table<string, DeathState>
---@field activeStage integer|nil
---@class DeathState
---@field isDead boolean
---@field pos Position
---@field heading number
---@field type string
---@field country_id integer
local persistanceWriteIntervalSeconds = 15
local enabled = false
---@type PersistentData
local tables = {
dead_units = {},
activeStage = nil
}
local logger = {}
if SpearheadConfig == nil then SpearheadConfig = {} end
if SpearheadConfig.Persistence == nil then SpearheadConfig.Persistence = {} end
local path = nil
local updateRequired = false
local createFileIfNotExists = function()
if not path then return end
local f = io.open(path, "r")
if f == nil then
f = io.open(path, "w+")
if f == nil then
logger:error("Could not create a file")
else
f:write("{}")
f:close()
end
else
f:close()
end
end
local loadTablesFromFile = function()
if not path then return end
logger:info("Loading data from persistance file...")
local f = io.open(path, "r")
if f == nil then
return
end
local json = f:read("*a")
local lua = net.json2lua(json)
if lua.activeStage then
logger:info("Found active stage from save: " .. lua.activeStage)
tables.activeStage = lua.activeStage
end
if lua.dead_units then
logger:debug("Found saved dead units")
for name, deadState in pairs(lua.dead_units) do
logger:debug("Found saved dead unit: " .. name)
if type(deadState) == "table" then
tables.dead_units[name] = {
isDead = deadState.isDead == true,
pos = deadState.pos,
heading = deadState.heading,
type = deadState.type,
country_id = deadState.country_id
}
end
end
end
f:close()
end
local writeToFile = function()
if not path then return end
local f = io.open(path, "w+")
if f == nil then
error("Could not open file for writing")
return
end
local jsonString = net.lua2json(tables)
f:write(jsonString)
if f ~= nil then
f:close()
end
end
local UpdateContinuous = function(null, time)
if updateRequired then
local status, result = pcall(writeToFile)
if status == false then
env.error("[Spearhead][Persistence] Could not write state to file: " .. result)
end
end
return time + persistanceWriteIntervalSeconds
end
Persistence.UpdateNow = function()
if enabled == true then
writeToFile()
end
end
Persistence.isEnabled = function()
return enabled
end
---comment
---@param persistenceLogger Logger
Persistence.Init = function(persistenceLogger)
logger = persistenceLogger
logger:info("Initiating Persistence Manager")
if lfs == nil or io == nil then
env.error("[Spearhead][Persistence] lfs and io seem to be sanitized. Persistence is skipped and disabled")
return
end
path = (SpearheadConfig.Persistence.directory or (lfs.writedir() .. "\\Data" )) .. "\\" .. (SpearheadConfig.Persistence.fileName or "Spearhead_Persistence.json")
createFileIfNotExists()
loadTablesFromFile()
timer.scheduleFunction(UpdateContinuous, nil, timer.getTime() + 120)
enabled = true
end
---Sets the stage in the persistence table
---@param stageNumber number
Persistence.SetActiveStage = function(stageNumber)
tables.activeStage = stageNumber
updateRequired = true
end
---Get the active stage as in the persistance file
---@return integer|nil
Persistence.GetActiveStage = function()
if tables.activeStage then
return tables.activeStage
end
return nil
end
---Check if the unit was dead during the last save. Nil if persitance not enabled
---@param unitName string name
---@return DeathState|nil { isDead, pos = {x,y,z}, heading, type, country_id }
Persistence.UnitDeadState = function(unitName)
if Persistence.isEnabled() == false then
return nil
end
local entry = tables.dead_units[unitName]
if entry then
return entry
else
return { isDead = false }
end
end
---Pass the unit to be saved as "dead"
---@param name string
---@param position Position { x, y ,z }
---@param heading number
---@param type string
---@param country_id number
Persistence.UnitKilled = function (name, position, heading, type, country_id)
if enabled == false then return end
tables.dead_units[name] = {
isDead = true,
pos = position,
heading = heading,
type = type,
country_id = country_id,
isCleaned = false
}
updateRequired = true
end
end
if Spearhead == nil then Spearhead = {} end
if Spearhead.classes == nil then Spearhead.classes = {} end
if Spearhead.classes.persistence == nil then Spearhead.classes.persistence = {} end
Spearhead.classes.persistence.Persistence = Persistence
+96 -46
View File
@@ -78,8 +78,14 @@ do -- INIT UTIL
---comment
---@param str string
---@param findable string
---@param ignoreCase boolean?
---@return boolean
UTIL.startswith = function(str, findable)
UTIL.startswith = function(str, findable, ignoreCase)
if ignoreCase == true then
return string.lower(str):find('^' .. string.lower(findable)) ~= nil
end
return str:find('^' .. findable) ~= nil
end
@@ -114,6 +120,14 @@ do -- INIT UTIL
end
end
---comment
---@param a table DCS Point vector {x, z , y}
---@param b table DCS Point vector {x, z , y}
---@return number
function UTIL.VectorDistance(a, b)
return math.sqrt((b.x - a.x) ^ 2 + (b.z - a.z) ^ 2)
end
---comment
---@param polygon table of pairs { x, z }
---@param x number X location
@@ -452,8 +466,8 @@ do -- INIT DCS_UTIL
return StaticObject.getByName(groupName) ~= nil
end
---comment
---@param groupName string destroy the given group
---destroy the given group
---@param groupName string
function DCS_UTIL.DestroyGroup(groupName)
if DCS_UTIL.IsGroupStatic(groupName) then
local object = StaticObject.getByName(groupName)
@@ -468,6 +482,22 @@ do -- INIT DCS_UTIL
end
end
---destroy the given unit
function DCS_UTIL.DestroyUnit(unitName)
if DCS_UTIL.IsGroupStatic(unitName) == true then
local object = StaticObject.getByName(unitName)
if object ~= nil then
object:destroy()
end
else
local unit = Unit.getByName(unitName)
if unit and unit:isExist() then
unit:destroy()
end
end
end
--- takes a list of units and returns all the units that are in any of the zones
---@param unit_names table unit names
---@param zone_names table zone names
@@ -604,7 +634,7 @@ do -- INIT DCS_UTIL
--- takes a x, y poistion and checks if it is inside any of the zones
---@param x number North South position
---@param z number West East position
---@param zone_name table zone names
---@param zone_name string zone name
---@return boolean result
function DCS_UTIL.isPositionInZone(x, z, zone_name)
local zone = DCS_UTIL.__trigger_zones[zone_name]
@@ -786,6 +816,37 @@ do -- INIT DCS_UTIL
return result
end
---Spawn an corpse
---@param countryId number countryId
---@param unitType string
---@param location table { z, y, z}
---@param heading number
function DCS_UTIL.SpawnCorpse(countryId, unitName, unitType, location, heading)
local name = "dead_" .. unitName
local staticObj = {
["heading"] = heading,
--["shape_name"] = "stolovaya",
["type"] = unitType,
["name"] = name,
["y"] = location.z,
["x"] = location.x,
["dead"] = true,
}
coalition.addStaticObject(countryId, staticObj)
end
function DCS_UTIL.CleanCorpse(unitName)
local unitName = "dead_" .. unitName
local object = StaticObject.getByName(unitName)
if object then
object:destroy()
end
end
--- spawns the units as specified in the mission file itself
--- location and route can be nil and will then use default route
---@param groupName string
@@ -793,19 +854,20 @@ do -- INIT DCS_UTIL
---@param route table? route of the group. If nil wil be the default route.
---@param uncontrolled boolean? Sets the group to be uncontrolled on spawn
---@return table? new_group the Group class that was spawned
---@return boolean? isStatic whether the group is a static or not
function DCS_UTIL.SpawnGroupTemplate(groupName, location, route, uncontrolled)
if groupName == nil then
return
return nil, nil
end
local template = DCS_UTIL.GetMizGroupOrDefault(groupName, nil)
if template == nil then
return nil
return nil, nil
end
if template.category == DCS_UTIL.GroupCategory.STATIC then
--TODO: Implement location and route stuff
local spawn_template = template.group_template
coalition.addStaticObject(template.country_id, spawn_template)
return coalition.addStaticObject(template.country_id, spawn_template), true
else
local spawn_template = template.group_template
if location ~= nil then
@@ -833,7 +895,7 @@ do -- INIT DCS_UTIL
spawn_template.uncontrolled = uncontrolled
end
local new_group = coalition.addGroup(template.country_id, template.category, spawn_template)
return new_group
return new_group, false
end
end
@@ -843,11 +905,14 @@ do -- INIT DCS_UTIL
bingoSetting = bingoSetting + offset
local group = Group.getByName(groupName)
for _, unit in pairs(group:getUnits()) do
if unit and unit:isExist() == true and unit:inAir() == true and unit:getFuel() < bingoSetting then
return true
if group then
for _, unit in pairs(group:getUnits()) do
if unit and unit:isExist() == true and unit:inAir() == true and unit:getFuel() < bingoSetting then
return true
end
end
end
return false
end
@@ -857,22 +922,25 @@ Spearhead.DcsUtil = DCS_UTIL
local LOGGER = {}
do
LOGGER.LogLevelOptions = {
DEBUG = 0,
INFO = 1,
WARN = 2,
ERROR = 3,
NONE = 4
}
local PreFix = "Spearhead"
function LOGGER:new(logger_name, logLevel, liveLoggingLevel)
--- @class Logger
--- @field debug fun(self:Logger, text:string)
--- @field info fun(self:Logger, text:string)
--- @field warn fun(self:Logger, text:string)
--- @field error fun(self:Logger, text:string)
---comment
---@param logger_name any
---@param logLevel LogLevel
---@return Logger
function LOGGER:new(logger_name, logLevel)
local o = {}
setmetatable(o, { __index = self })
o.LoggerName = logger_name or "(loggername not set)"
o.LogLevel = logLevel or LOGGER.LogLevelOptions.INFO
o.LiveLoggingLevel = liveLoggingLevel or LOGGER.LogLevelOptions.NONE
o.LogLevel = logLevel or "INFO"
---comment
---@param self table self logger
@@ -883,13 +951,9 @@ do
end
message = UTIL.toString(message)
if self.LogLevel <= LOGGER.LogLevelOptions.INFO then
if self.LogLevel == "INFO" or self.LogLevel == "DEBUG" then
env.info("[" .. PreFix .. "]" .. "[" .. self.LoggerName .. "] " .. message)
end
if self.LiveLoggingLevel <= LOGGER.LogLevelOptions.INFO then
trigger.action.outText(message, 20)
end
end
---comment
@@ -900,12 +964,8 @@ do
end
message = UTIL.toString(message)
if self.LogLevel <= LOGGER.LogLevelOptions.WARN then
env.warning("[" .. PreFix .. "]" .. "[" .. self.LoggerName .. "] " .. message)
end
if self.LiveLoggingLevel <= LOGGER.LogLevelOptions.WARN then
trigger.action.outText(message, 20)
if self.LogLevel == "INFO" or self.LogLevel == "DEBUG" or self.LogLevel == "WARN" then
env.info("[" .. PreFix .. "]" .. "[" .. self.LoggerName .. "] " .. message)
end
end
@@ -919,12 +979,8 @@ do
message = UTIL.toString(message)
if self.LogLevel <= LOGGER.LogLevelOptions.ERROR then
env.error("[" .. PreFix .. "]" .. "[" .. self.LoggerName .. "] " .. message)
end
if self.LiveLoggingLevel <= LOGGER.LogLevelOptions.ERROR then
trigger.action.outText(message, 20)
if self.LogLevel == "INFO" or self.LogLevel == "DEBUG" or self.LogLevel == "WARN" or self.logLevel == "ERROR" then
env.info("[" .. PreFix .. "]" .. "[" .. self.LoggerName .. "] " .. message)
end
end
@@ -937,13 +993,9 @@ do
end
message = UTIL.toString(message)
if self.LogLevel <= LOGGER.LogLevelOptions.DEBUG then
if self.LogLevel == "DEBUG" then
env.info("[" .. PreFix .. "]" .. "[" .. self.LoggerName .. "][DEBUG] " .. message)
end
if self.LiveLoggingLevel <= LOGGER.LogLevelOptions.DEBUG then
trigger.action.outText(message, 20)
end
end
@@ -957,15 +1009,13 @@ function Spearhead.AddMissionEditorWarning(warningMessage)
table.insert(Spearhead.MissionEditingWarnings, warningMessage or "skip")
end
missionCommands.addSubMenu("Missions")
local loadDone = false
Spearhead.LoadingDone = function()
if loadDone == true then
return
end
local warningLogger = Spearhead.LoggerTemplate:new("MISSIONPARSER", Spearhead.LoggerTemplate.LogLevelOptions.INFO, 4)
local warningLogger = Spearhead.LoggerTemplate:new("MISSIONPARSER", "INFO")
if Spearhead.Util.tableLength(Spearhead.MissionEditingWarnings) > 0 then
for key, message in pairs(Spearhead.MissionEditingWarnings) do
warningLogger:warn(message)
+600 -596
View File
File diff suppressed because it is too large Load Diff
+74 -57
View File
@@ -1,26 +1,39 @@
local SpearheadEvents = {}
do
---@type Logger
local logger = nil
---@param logLevel LogLevel
SpearheadEvents.Init = function(logLevel)
logger = Spearhead.LoggerTemplate:new("Events", logLevel)
end
local warn = function(text)
if logger then
logger:warn(text)
end
end
local logError = function(text)
if logger then logger:error(text) end
end
local logDebug = function(text)
if logger then logger:debug(text) end
end
---@class OnStageChangedListener
---@field OnStageNumberChanged fun(self:OnStageChangedListener, number:integer)
do -- STAGE NUMBER CHANGED
local OnStageNumberChangedListeners = {}
local OnStageNumberChangedHandlers = {}
local warn = function(text)
env.warn("[Spearhead][Events] " .. (text or "nil"))
end
local error = function(text)
env.error("[Spearhead][Events] " .. (text or "nil"))
end
---Add a stage zone number changed listener
---@param listener table object with function OnStageNumberChanged(self, number)
---@param listener OnStageChangedListener object with function OnStageNumberChanged(self, number)
SpearheadEvents.AddStageNumberChangedListener = function(listener)
if type(listener) ~= "table" then
warn("Event listener not of type table, did you mean to use handler?")
return
end
table.insert(OnStageNumberChangedListeners, listener)
end
@@ -36,21 +49,27 @@ do
---@param newStageNumber number
SpearheadEvents.PublishStageNumberChanged = function(newStageNumber)
pcall(function ()
Spearhead.classes.persistence.Persistence.SetActiveStage(newStageNumber)
end)
for _, callable in pairs(OnStageNumberChangedListeners) do
local succ, err = pcall(function()
callable:OnStageNumberChanged(newStageNumber)
end)
if err then
error(err)
logError(err)
end
end
for _, callable in pairs(OnStageNumberChangedHandlers) do
local succ, err = pcall(callable, newStageNumber)
if err then
error(err)
logError(err)
end
end
Spearhead.StageNumber = newStageNumber
end
end
@@ -72,11 +91,15 @@ do
table.insert(onLandEventListeners[unitName], landListener)
end
---@class OnUnitLostListener
---@field OnUnitLost fun(self:OnUnitLostListener, unit:table)
---@type table<string,Array<OnUnitLostListener>>
local OnUnitLostListeners = {}
---This listener gets fired for any event that can indicate a loss of a unit.
---Such as: Eject, Crash, Dead, Unit_Lost,
---@param unitName any
---@param unitLostListener table Object with function: OnUnitLost(initiatorUnit)
---@param unitLostListener OnUnitLostListener
SpearheadEvents.addOnUnitLostEventListener = function(unitName, unitLostListener)
if type(unitLostListener) ~= "table" then
warn("Unit lost Event listener not of type table/object")
@@ -119,7 +142,7 @@ do
callable:OnGroupRTB(groupName)
end)
if err then
error(err)
logError(err)
end
end
end
@@ -154,7 +177,7 @@ do
callable:OnGroupRTBInTen(groupName)
end)
if err then
error(err)
logError(err)
end
end
end
@@ -190,7 +213,7 @@ do
callable:OnGroupOnStation(groupName)
end)
if err then
error(err)
logError(err)
end
end
end
@@ -198,39 +221,6 @@ do
end
end
do --COMMANDS
do -- status updates
local onStatusRequestReceivedListeners = {}
---comment
---@param listener table object with OnStatusRequestReceived(self, groupId)
SpearheadEvents.AddOnStatusRequestReceivedListener = function(listener)
if type(listener) ~= "table" then
warn("Unit lost Event listener not of type table/object")
return
end
table.insert(onStatusRequestReceivedListeners, listener)
end
local triggerStatusRequestReceived = function(groupId)
for _, callable in pairs(onStatusRequestReceivedListeners) do
local succ, err = pcall(function()
callable:OnStatusRequestReceived(groupId)
end)
end
end
SpearheadEvents.AddCommandsToGroup = function(groupId)
local base = "MISSIONS"
if groupId then
missionCommands.addCommandForGroup(groupId, "Stage Status", nil, triggerStatusRequestReceived,
groupId)
end
end
end
end
do -- PLAYER ENTER UNIT
local playerEnterUnitListeners = {}
---comment
@@ -252,7 +242,7 @@ do
callable:OnPlayerEntersUnit(unit)
end)
if err then
error(err)
logError(err)
end
end
end
@@ -260,6 +250,20 @@ do
end
end
do -- Ejection events
local unitEjectListeners = {}
SpearheadEvents.AddOnUnitEjectedListener = function(listener)
if type(listener) ~= "table" then
warn("Unit lost Event listener not of type table/object")
return
end
table.insert(unitEjectListeners, listener)
end
end
local e = {}
function e:onEvent(event)
if event.id == world.event.S_EVENT_LAND or event.id == world.event.S_EVENT_RUNWAY_TOUCH then
@@ -273,7 +277,7 @@ do
callable:OnUnitLanded(unit, airbase)
end)
if err then
error(err)
logError(err)
end
end
end
@@ -285,6 +289,11 @@ do
event.id == world.event.S_EVENT_EJECTION or
event.id == world.event.S_EVENT_UNIT_LOST then
local object = event.initiator
if object and object.getName then
logDebug("Receiving death event from: " .. object:getName())
end
if object and object.getName and OnUnitLostListeners[object:getName()] then
for _, callable in pairs(OnUnitLostListeners[object:getName()]) do
local succ, err = pcall(function()
@@ -292,12 +301,20 @@ do
end)
if err then
error(err)
logError(err)
end
end
end
end
if event.id == world.event.S_EVENT_EJECTION then
end
if event.id == world.event.S_EVENT_MISSION_END then
Spearhead.classes.persistence.Persistence.UpdateNow()
end
local AI_GROUPS = {}
local function CheckAndTriggerSpawnAsync(unit, time)
+195 -21
View File
@@ -1,47 +1,221 @@
local StagesByName = {}
---@type table<string, Array<Stage>>
local StagesByIndex = {}
---@type table<string, Array<Stage>>
local SideStageByIndex = {}
---@type table<string, Array<WaitingStage>>
local WaitingStagesByIndex = {}
local currentStage = -99
GlobalStageManager = {}
---comment
---@param database Database
---@param stageConfig StageConfig
---@return nil
function GlobalStageManager:NewAndStart(database, stageConfig)
local logger = Spearhead.LoggerTemplate:new("StageManager", stageConfig.logLevel)
logger:info("Using Stage Log Level: " .. stageConfig.logLevel)
local o = {}
setmetatable(o, { __index = self })
o.logger = logger
o.onStageCompleted = function(self, stage)
local stageNumber = tostring(stage.stageNumber)
local anyActive = false
for _, stage in pairs(StagesByIndex[stageNumber] or {}) do
if stage.isActive then anyActive = true end
end
if anyActive == false and stageConfig:isAutoStages() == true then
Spearhead.Events.PublishStageNumberChanged(tonumber(stageNumber) + 1)
end
if stageConfig.isAutoStages ~= true then
logger:warn("Spearhead will not automatically progress stages due to the given settings. If you manually have implemented this, please ignore this message")
end
---@type OnStageChangedListener
local OnStageNumberChangedListener = {
OnStageNumberChanged = function (self, number)
currentStage = number
end
}
Spearhead.Events.AddStageNumberChangedListener(OnStageNumberChangedListener)
---@type StageCompleteListener
local OnStageCompleteListener = {
OnStageComplete = function(self, stage)
logger:debug("Receiving stage complete event from: " .. stage.zoneName)
local anyIncomplete = false
logger:debug("Checking stages for index: " .. tostring(currentStage))
for index, stage in pairs(StagesByIndex[tostring(currentStage)]) do
if stage:IsComplete() == false then
anyIncomplete = true
logger:debug("Need to wait for Stage " .. stage.zoneName .. " to be completed")
else
logger:debug("Stage verified to be completed: " .. stage.zoneName)
end
end
if anyIncomplete == false and stageConfig.isAutoStages == true then
-- CHECK WAITING STAGES
local nextStage = currentStage + 1
if WaitingStagesByIndex[tostring(nextStage)] then
for _, waitingStage in pairs(WaitingStagesByIndex[tostring(nextStage)]) do
if waitingStage:IsActive() == false then
waitingStage:ActivateStage()
end
end
end
local anyWaiting = false
if WaitingStagesByIndex[tostring(nextStage)] then
for _, waitingStage in pairs(WaitingStagesByIndex[tostring(nextStage)]) do
if waitingStage:IsComplete() == false then
anyWaiting = true
end
end
end
if anyWaiting == false then
logger:debug("Setting next stage to: " .. tostring(currentStage + 1))
Spearhead.Events.PublishStageNumberChanged(currentStage + 1)
end
end
end
}
for _, stageName in pairs(database:getStagezoneNames()) do
local stagelogger = Spearhead.LoggerTemplate:new(stageName, stageConfig.logLevel)
local stage = Spearhead.internal.Stage:new(stageName, database, stagelogger, stageConfig)
if Spearhead.Util.startswith(stageName, "missionstage", true) then
local valid = true
local split = Spearhead.Util.split_string(stageName, "_")
if Spearhead.Util.tableLength(split) < 2 then
Spearhead.AddMissionEditorWarning("Stage zone with name " .. stageName .. " does not have a order number or valid format")
valid = false
end
if stage then
stage:AddStageCompleteListener(o);
StagesByName[stageName] = stage
local indexString = tostring(stage.stageNumber)
if StagesByIndex[indexString] == nil then StagesByIndex[indexString] = {} end
table.insert(StagesByIndex[indexString], stage)
logger:info("Initiated " .. Spearhead.Util.tableLength(StagesByName) .. " airbases for cap")
if Spearhead.Util.tableLength(split) < 3 then
Spearhead.AddMissionEditorWarning("Stage zone with name " .. stageName .. " does not have a stage name")
valid = false
end
local orderNumber = nil
local isSideStage = false
if valid == true then
local orderNumberString = string.lower(split[2])
if Spearhead.Util.startswith(orderNumberString, "x") == true then
isSideStage = true
local orderNumberString = string.gsub(orderNumberString, "x", "")
orderNumber = tonumber(orderNumberString)
else
orderNumber = tonumber(split[2])
end
if orderNumber == nil then
Spearhead.AddMissionEditorWarning("Stage zone with name " .. stageName .. " does not have a valid order number : " .. split[2])
valid = false
end
end
local stageDisplayName = split[3]
local stagelogger = Spearhead.LoggerTemplate:new(stageName, stageConfig.logLevel)
if valid == true and orderNumber then
---@type StageInitData
local initData = {
stageDisplayName = stageDisplayName,
stageNumber = orderNumber,
stageZoneName = stageName,
}
if isSideStage == true then
local stage = Spearhead.classes.stageClasses.Stages.ExtraStage.New(database, stageConfig, stagelogger, initData)
stage:AddStageCompleteListener(OnStageCompleteListener)
if SideStageByIndex[tostring(orderNumber)] == nil then SideStageByIndex[tostring(orderNumber)] = {} end
table.insert(SideStageByIndex[tostring(orderNumber)], stage)
else
local stage = Spearhead.classes.stageClasses.Stages.PrimaryStage.New(database, stageConfig, stagelogger, initData)
stage:AddStageCompleteListener(OnStageCompleteListener)
if StagesByIndex[tostring(orderNumber)] == nil then StagesByIndex[tostring(orderNumber)] = {} end
table.insert(StagesByIndex[tostring(orderNumber)], stage)
end
end
end
if Spearhead.Util.startswith(stageName, "waitingstage", true) then
local valid = true
local split = Spearhead.Util.split_string(stageName, "_")
if Spearhead.Util.tableLength(split) < 3 then
Spearhead.AddMissionEditorWarning("Stage zone with name " .. stageName .. " does not have a order number or valid format")
valid = false
end
if valid == true then
local stageIndexString = split[2]
local stageIndex = tonumber(stageIndexString)
if not stageIndex then
Spearhead.AddMissionEditorWarning("Stage zone with name " .. stageName .. " does not have a valid order number")
valid = false
end
local waitingSecondsString = split[3]
local waitingSeconds = tonumber(waitingSecondsString)
if not waitingSeconds then
Spearhead.AddMissionEditorWarning("Waiting Stage zone with name " .. stageName .. " does not have a valid amount of seconds parameter")
valid = false
end
if valid == true then
local stagelogger = Spearhead.LoggerTemplate:new(stageName, stageConfig.logLevel)
---@type WaitingStageInitData
local initData = {
stageDisplayName = "Waiting Stage " .. stageIndex,
stageNumber = stageIndex or -99,
stageZoneName = stageName,
waitingSeconds = waitingSeconds --[[@as integer]]
}
local waitingStage = Spearhead.classes.stageClasses.Stages.WaitingStage.New(database, stageConfig, stagelogger, initData)
if WaitingStagesByIndex[tostring(stageIndex)] == nil then
WaitingStagesByIndex[tostring(stageIndex)] = {}
end
table.insert(WaitingStagesByIndex[tostring(stageIndex)], waitingStage)
waitingStage:AddStageCompleteListener(OnStageCompleteListener)
end
end
end
end
return o
end
---comment
---@param stageNumber number
---@return boolean | nil
GlobalStageManager.isStageComplete = function (stageNumber)
local stageIndex = tostring(stageNumber)
if StagesByIndex[stageIndex] == nil then return nil end
for _, stage in ipairs(StagesByIndex[stageIndex]) do
if stage:IsComplete() == false then
return false
end
end
return true
end
if not Spearhead.internal then Spearhead.internal = {} end
Spearhead.internal.GlobalStageManager = GlobalStageManager
@@ -0,0 +1,115 @@
---@class SpearheadGroup : OnUnitLostListener
---@field groupName string
---@field private isStatic boolean
---@field private isSpawned boolean
local SpearheadGroup = {}
function SpearheadGroup.New(groupName)
SpearheadGroup.__index = SpearheadGroup
local o = {}
local self = setmetatable(o, SpearheadGroup)
self.isStatic = Spearhead.DcsUtil.IsGroupStatic(groupName) == true
self.groupName = groupName
self.isSpawned = false
return self
end
function SpearheadGroup:SpawnCorpsesOnly()
if self.isSpawned == true then return end
local group = Spearhead.DcsUtil.SpawnGroupTemplate(self.groupName)
if group then
for _, unit in pairs(group:getUnits()) do
local deathState = Spearhead.classes.persistence.Persistence.UnitDeadState(unit:getName())
if deathState and deathState.isDead == true then
Spearhead.DcsUtil.DestroyUnit(self.groupName, unit:getName())
Spearhead.DcsUtil.SpawnCorpse(deathState.country_id, unit:getName(), deathState.type, deathState.pos, deathState.heading)
end
end
end
self.isSpawned = true
end
function SpearheadGroup:Spawn()
if self.isSpawned == true then return end
local group = Spearhead.DcsUtil.SpawnGroupTemplate(self.groupName)
if group then
for _, unit in pairs(group:getUnits()) do
local deathState = Spearhead.classes.persistence.Persistence.UnitDeadState(unit:getName())
if deathState and deathState.isDead == true then
Spearhead.DcsUtil.DestroyUnit(self.groupName, unit:getName())
Spearhead.DcsUtil.SpawnCorpse(deathState.country_id, unit:getName(), deathState.type, deathState.pos, deathState.heading)
else
Spearhead.Events.addOnUnitLostEventListener(unit:getName(), self)
end
Spearhead.Events.addOnUnitLostEventListener(unit:getName(), self)
end
end
self.isSpawned = true
end
function SpearheadGroup:Destroy()
self.isSpawned = false
Spearhead.DcsUtil.DestroyGroup(self.groupName)
end
---comment
---@return integer
function SpearheadGroup:GetCoalition()
if self.isStatic == true then
local object = StaticObject.getByName(self.groupName)
return object:getCoalition()
else
local group = Group.getByName(self.groupName)
return group:getCoalition()
end
end
function SpearheadGroup:OnUnitLost(object)
local name = object:getName()
local pos = object:getPoint()
local type = object:getDesc().typeName
local position = object:getPosition()
local heading = math.atan2(position.x.z, position.x.x)
local country_id = object:getCountry()
Spearhead.classes.persistence.Persistence.UnitKilled(name, pos, heading, type, country_id)
end
---comment
---@return table result list of objects
function SpearheadGroup:GetUnits()
local result = {}
if self.isStatic == true then
local staticObject = StaticObject.getByName(self.groupName)
if staticObject then
table.insert(result, staticObject)
end
else
local group = Group.getByName(self.groupName)
for _, unit in pairs(group:getUnits()) do
table.insert(result, unit)
end
end
return result
end
if not Spearhead.classes then Spearhead.classes = {} end
if not Spearhead.classes.stageClasses then Spearhead.classes.stageClasses = {} end
if not Spearhead.classes.stageClasses.Groups then Spearhead.classes.stageClasses.Groups = {} end
Spearhead.classes.stageClasses.Groups.SpearheadGroup = SpearheadGroup
-401
View File
@@ -1,401 +0,0 @@
--- A mission Object.
local Mission = {}
do -- INIT Mission Class
local MINIMAL_UNITS_ALIVE_RATIO = 0.20
local Defaults = {}
Defaults.MainMenu = "Missions"
Defaults.SelectMenuSubMenus = { Defaults.MainMenu, "Select Mission" }
Defaults.ShowMissionSubs = { Defaults.MainMenu }
local PlayersInMission = {}
local MissionType = {
UNKNOWN = 0,
STRIKE = 1,
BAI = 2,
DEAD = 3,
SAM = 4,
}
do --INIT MISSION TYPE FUNCTIONS
---Parse string to mission type
---@param input string
MissionType.Parse = function(input)
if input == nil then
return Mission.MissionType.UNKNOWN
end
input = string.lower(input)
if input == "dead" then return MissionType.DEAD end
if input == "strike" then return MissionType.STRIKE end
if input == "bai" then return MissionType.BAI end
if input == "sam" then return MissionType.SAM end
return Mission.MissionType.UNKNOWN
end
---comment
---@param input number missionType
---@return string text
MissionType.toString = function(input)
if input == MissionType.DEAD then return "DEAD" end
if input == MissionType.STRIKE then return "STRIKE" end
if input == MissionType.BAI then return "BAI" end
if input == MissionType.SAM then return "SAM" end
return "?"
end
end
Mission.MissionType = MissionType
Mission.MissionState = {
NEW = 0,
ACTIVE = 1,
COMPLETED = 2,
}
---comment
---@param missionZoneName string missionZoneName
---@param database table db dependency injection
---@return table?
function Mission:new(missionZoneName, database, logger)
local o = {}
setmetatable(o, { __index = self })
local function ParseGroupName(input)
local split_name = Spearhead.Util.split_string(input, "_")
local split_length = Spearhead.Util.tableLength(split_name)
if Spearhead.Util.startswith(input, "RANDOMMISSION") == true and split_length < 4 then
Spearhead.AddMissionEditorWarning("Random Mission with zonename " .. input .. " not in right format")
return nil
elseif split_length < 3 then
Spearhead.AddMissionEditorWarning("Mission with zonename" .. input .. " not in right format")
return nil
end
local type = split_name[2]
local parsedType = Mission.MissionType.Parse(type)
if parsedType == nil then
Spearhead.AddMissionEditorWarning("Mission with zonename '" .. input .. "' has an unsupported type '" .. (type or "nil" ))
return nil
end
local name = split_name[3]
return {
missionName = name,
type = parsedType
}
end
local parsed = ParseGroupName(missionZoneName)
if parsed == nil then return nil end
o.missionZoneName = missionZoneName
o.database = database
o.groupNames = database:getGroupsForMissionZone(missionZoneName)
o.name = parsed.missionName
o.missionType = parsed.type
o.missionTypeDisplayName = Mission.MissionType.toString(o.missionType)
o.startingGroups = Spearhead.Util.tableLength(o.groupNames)
o.missionState = Mission.MissionState.NEW
o.missionbriefing = database:GetDescriptionForMission(missionZoneName)
o.startingUnits = 0
o.logger = logger
o.code = database:GetNewMissionCode()
o.groupNamesPerUnit = {}
o.groupUnitAliveDict = {}
o.targetAliveStates = {}
o.hasSpecificTargets = false
local CheckStateAsync = function (self, time)
self:CheckAndUpdateSelf()
return nil
end
o.GetState = function(self)
return self.missionState
end
o.OnUnitLost = function(self, object)
--[[
OnUnit lost event
]]--
self.logger:debug("Getting on unit lost event")
local category = Object.getCategory(object)
if category == Object.Category.UNIT then
local unitName = object:getName()
self.logger:debug("UnitName:" .. unitName)
local groupName = self.groupNamesPerUnit[unitName]
self.groupUnitAliveDict[groupName][unitName] = false
if self.targetAliveStates[groupName][unitName] then
self.targetAliveStates[groupName][unitName] = false
end
elseif category == Object.Category.STATIC then
local name = object:getName()
self.groupUnitAliveDict[name][name] = false
self.logger:debug("Name " .. name)
if self.targetAliveStates[name][name] then
self.targetAliveStates[name][name] = false
end
end
timer.scheduleFunction(CheckStateAsync, self, timer.getTime() + 1)
end
o.MissionCompleteListeners = {}
---comment
---@param self table
---@param listener table Object that implements "OnMissionComplete(self, mission)"
o.AddMissionCompleteListener = function(self, listener)
if type(listener) ~= "table" then
return
end
table.insert(self.MissionCompleteListeners, listener)
end
local TriggerMissionComplete = function(self)
for _, callable in pairs(self.MissionCompleteListeners) do
local succ, err = pcall( function()
callable:OnMissionComplete(self)
end)
if err then
self.logger:warn("Error in misstion complete listener:" .. err)
end
end
end
local StartCheckingAndUpdateSelfContinuous = function (self)
local CheckAndUpdate = function(self, time)
self:CheckAndUpdateSelf(true)
if self.missionState == Mission.MissionState.COMPLETED or self.missionState == Mission.MissionState.NEW then
return nil
else
return time + 60
end
end
timer.scheduleFunction(CheckAndUpdate, self, timer.getTime() + 300)
end
---comment
---@param self table
---@param checkUnitHealth boolean?
o.CheckAndUpdateSelf = function(self, checkUnitHealth)
if not checkUnitHealth then checkUnitHealth = false end
if checkUnitHealth == true then
local function unitAliveState(unitName)
local unit = Unit.getByName(unitName)
return unit ~= nil and unit:isExist() == true and unit:getLife() > 0.1
end
for groupName, unitNameDict in pairs(self.groupUnitAliveDict) do
for unitName, isAlive in pairs(unitNameDict) do
if isAlive == true then
self.groupUnitAliveDict[groupName][unitName] = unitAliveState(unitName)
end
end
end
for groupName, unitNameDict in pairs(self.targetAliveStates) do
for unitName, isAlive in pairs(unitNameDict) do
if isAlive == true then
self.targetAliveStates[groupName][unitName] = unitAliveState(unitName)
end
end
end
end
if self.missionState == Mission.MissionState.COMPLETED then
return
end
if self.hasSpecificTargets == true then
local specificTargetsAlive = false
for groupName, unitNameDict in pairs(self.targetAliveStates) do
for unitName, isAlive in pairs(unitNameDict) do
if isAlive == true then
specificTargetsAlive = true
end
end
end
if specificTargetsAlive == false then
self.missionState = Mission.MissionState.COMPLETED
end
else
local function CountAliveGroups()
self.logger:debug(self.groupUnitAliveDict)
local aliveGroups = 0
for _, group in pairs(self.groupUnitAliveDict) do
local groupTotal = 0
local groupDeath = 0
for _, isAlive in pairs(group) do
if isAlive ~= true then
groupDeath = groupDeath + 1
end
groupTotal = groupTotal + 1
end
local aliveRatio = (groupTotal - groupDeath) / groupTotal
if aliveRatio >= MINIMAL_UNITS_ALIVE_RATIO then
aliveGroups = aliveGroups + 1
end
end
return aliveGroups
end
if self.missionType == Mission.MissionType.STRIKE then --strike targets should normally have TGT targets
if CountAliveGroups() == 0 then
self.missionState = Mission.MissionState.COMPLETED
end
elseif self.missionType == Mission.MissionType.BAI then
if CountAliveGroups() == 0 then
self.missionState = Mission.MissionState.COMPLETED
end
end
--[[
TODO: Other checks for mission complete
]]
end
if self.missionState == Mission.MissionState.COMPLETED then
self.logger:debug("Mission complete " .. self.name)
trigger.action.outText("Mission " .. self.name .. " (" .. self.code .. ") was completed succesfully!", 20)
TriggerMissionComplete(self)
--Schedule cleanup after 5 minutes of mission complete
--timer.scheduleFunction(CleanupDelayedAsync, self, timer.getTime() + 300)
end
end
---Activates groups for this mission
---@param self table
o.Activate = function(self)
if self.missionState == Mission.MissionState.ACTIVE then
return
end
self.missionState = Mission.MissionState.ACTIVE
do --spawn groups
for key, groupname in pairs(self.groupNames) do
Spearhead.DcsUtil.SpawnGroupTemplate(groupname)
end
end
StartCheckingAndUpdateSelfContinuous(self)
end
local ToStateString = function(self)
if self.hasSpecificTargets then
local dead = 0
local total = 0
for _, group in pairs(self.targetAliveStates) do
for _, isAlive in pairs(group) do
total = total + 1
if isAlive == false then
dead = dead + 1
end
end
end
local completionPercentage = math.floor((dead / total) * 100)
return "Targets Destroyed: " .. completionPercentage .. "%"
else
local dead = 0
local total = 0
for _, group in pairs(self.groupUnitAliveDict) do
for _, isAlive in pairs(group) do
total = total + 1
if isAlive == false then
dead = dead + 1
end
end
end
local completionPercentage = math.floor((dead / total) * 100)
return "Targets Destroyed: " .. completionPercentage .. "%"
end
end
o.ShowBriefing = function(self, groupId)
local stateString = ToStateString(self)
if self.missionbriefing == nil then self.missionbriefing = "No briefing available" end
local text = "Mission [" .. self.code .. "] ".. self.name .. "\n \n" .. self.missionbriefing .. " \n \n" .. stateString
trigger.action.outTextForGroup(groupId, text, 30);
end
o.Cleanup = function(self)
for key, groupName in pairs(self.groupNames) do
Spearhead.DcsUtil.DestroyGroup(groupName)
end
end
local Init = function(self)
for key, group_name in pairs(self.groupNames) do
self.groupUnitAliveDict[group_name] = {}
self.targetAliveStates[group_name] = {}
if Spearhead.DcsUtil.IsGroupStatic(group_name) then
Spearhead.Events.addOnUnitLostEventListener(group_name, self)
if Spearhead.Util.startswith(group_name, "TGT_") == true then
self.targetAliveStates[group_name][group_name] = true
self.hasSpecificTargets = true
end
else
local group = Group.getByName(group_name)
local isGroupTarget = Spearhead.Util.startswith(group_name, "TGT_")
self.startingUnits = self.startingUnits + group:getInitialSize()
for _, unit in pairs(group:getUnits()) do
local unitName = unit:getName()
self.groupNamesPerUnit[unitName] = group_name
Spearhead.Events.addOnUnitLostEventListener(unitName, self)
if isGroupTarget == true or Spearhead.Util.startswith(unitName, "TGT_") == true then
self.targetAliveStates[group_name][unitName] = true
self.hasSpecificTargets = true
end
if self.missionType == MissionType.BAI then
if Spearhead.DcsUtil.IsGroupStatic(group_name) ~= true then
self.groupUnitAliveDict[group_name][unitName] = true
end
elseif self.missionType == MissionType.DEAD or self.missionType == MissionType.SAM then
local desc = unit:getDesc()
local attributes = desc.attributes
if attributes["SAM"] == true or attributes["SAM TR"] or attributes["AAA"] then
self.targetAliveStates[group_name][unitName] = true
self.hasSpecificTargets = true
end
else
self.groupUnitAliveDict[group_name][unitName] = true
end
end
end
Spearhead.DcsUtil.DestroyGroup(group_name)
end
end
Init(o)
return o;
end
end
if not Spearhead.internal then Spearhead.internal = {} end
Spearhead.internal.Mission = Mission
+404
View File
@@ -0,0 +1,404 @@
---@class Mission : OnUnitLostListener
---@field name string
---@field missionType missionType
---@field code string
---@field priority MissionPriority
---@field private _state MissionState
---@field private _zoneName string
---@field private _database Database
---@field private _logger Logger
---@field private _missionBriefing string
---@field private _missionGroups MissionGroups
---@field private _completeListeners Array<MissionCompleteListener>
local Mission = {}
--- @class MissionCompleteListener
--- @field OnMissionComplete fun(self: any, mission:Mission)
--- @class MissionGroups
--- @field hasTargets boolean
--- @field groups Array<SpearheadGroup>
--- @field unitsAlive table<string, table<string, boolean>>
--- @field targetsAlive table<string, table<string, boolean>>
--- @field groupNamesPerunit table<string,string>
MINIMAL_UNITS_ALIVE_RATIO = 0.21
---comment
---@param zoneName string
---@param priority MissionPriority
---@param database Database
---@param logger Logger
---@return Mission?
function Mission.New(zoneName, priority, database, logger)
local function ParseZoneName(input)
local split_name = Spearhead.Util.split_string(input, "_")
local split_length = Spearhead.Util.tableLength(split_name)
if Spearhead.Util.startswith(input, "RANDOMMISSION") == true and split_length < 4 then
Spearhead.AddMissionEditorWarning("Random Mission with zonename " .. input .. " not in right format")
return nil
elseif split_length < 3 then
Spearhead.AddMissionEditorWarning("Mission with zonename" .. input .. " not in right format")
return nil
end
---@type missionType
local parsedType = "nil"
local inputType = string.lower(split_name[2])
if inputType == "dead" then parsedType = "DEAD" end
if inputType == "strike" then parsedType = "STRIKE" end
if inputType == "bai" then parsedType = "BAI" end
if inputType == "sam" then parsedType = "SAM" end
if parsedType == "nil" then
Spearhead.AddMissionEditorWarning("Mission with zonename '" .. input .. "' has an unsupported type '" .. (type or "nil" ))
return nil
end
local name = split_name[3]
return {
missionName = name,
type = parsedType
}
end
local parsed = ParseZoneName(zoneName)
if parsed == nil then return end
Mission.__index = Mission
local o = {}
local self = setmetatable(o, Mission)
self._zoneName = zoneName
self.name = parsed.missionName
self.missionType = parsed.type
self.code = tostring(database:GetNewMissionCode())
self.priority = priority
self._state = "NEW"
self._logger = logger
self._database = database
self._completeListeners = {}
self._missionBriefing = database:getMissionBriefingForMissionZone(zoneName)
self._missionGroups = {
groups = {},
unitsAlive = {},
targetsAlive = {},
hasTargets = false,
groupNamesPerunit = {}
}
local SpearheadGroup = Spearhead.classes.stageClasses.Groups.SpearheadGroup
local groupNames = database:getGroupsForMissionZone(zoneName)
for _, groupName in pairs(groupNames) do
local spearheadGroup = SpearheadGroup.New(groupName)
table.insert(self._missionGroups.groups, spearheadGroup)
local isGroupTarget =Spearhead.Util.startswith(string.lower(groupName), "tgt_")
for _, unit in pairs(spearheadGroup:GetUnits())do
local unitName = unit:getName()
local isUnitTarget = Spearhead.Util.startswith(string.lower(unitName), "tgt_")
if self._missionGroups.unitsAlive[groupName] == nil then
self._missionGroups.unitsAlive[groupName] = {}
end
self._missionGroups.unitsAlive[groupName][unitName] = true
self._missionGroups.groupNamesPerunit[unitName] = groupName
if isGroupTarget == true or isUnitTarget == true then
self._missionGroups.hasTargets = true
if self._missionGroups.targetsAlive[groupName] == nil then
self._missionGroups.targetsAlive[groupName] = {}
end
self._missionGroups.targetsAlive[groupName][unitName] = true
end
Spearhead.Events.addOnUnitLostEventListener(unitName, self)
end
Spearhead.DcsUtil.DestroyGroup(groupName)
end
return self
end
---comment
---@return MissionState
function Mission:GetState()
return self._state
end
function Mission:SpawnPersistedState()
for _, group in pairs(self._missionGroups.groups) do
group:SpawnCorpsesOnly()
end
end
function Mission:SpawnActive()
self._logger:info("Activating " .. self.name)
self._state = "ACTIVE"
for _, group in pairs(self._missionGroups.groups) do
group:Spawn()
end
Spearhead.classes.stageClasses.helpers.MissionCommandsHelper.AddMissionToCommands(self)
self:StartCheckingContinuous()
end
---@private
function Mission:StartCheckingContinuous()
---comment
---@param mission Mission
---@param time any
---@return unknown
local Check = function (mission, time)
mission:UpdateState(true, true)
if mission:GetState() == "COMPLETED" then
return nil
end
return time + 30
end
timer.scheduleFunction(Check, self, timer.getTime() + 30)
end
---@private
---@return string?
function Mission:ToStateString()
if self._missionGroups.hasTargets == true then
local dead = 0
local total = 0
if self._missionGroups.targetsAlive then
for _, group in pairs(self._missionGroups.targetsAlive) do
for _, isAlive in pairs(group) do
total = total + 1
if isAlive == false then
dead = dead + 1
end
end
end
end
if total > 0 then
local completionPercentage = math.floor((dead / total) * 100)
return "Targets Destroyed: " .. completionPercentage .. "%"
end
else
local dead = 0
local total = 0
if self._missionGroups.unitsAlive then
for _, group in pairs(self._missionGroups.unitsAlive) do
for _, isAlive in pairs(group) do
total = total + 1
if isAlive == false then
dead = dead + 1
end
end
end
end
if total > 0 then
local completionPercentage = math.floor((dead / total) * 100)
return "Units Destroyed: " .. completionPercentage .. "%"
end
end
end
---comment
---@param groupId integer
function Mission:ShowBriefing(groupId)
local stateString = self:ToStateString()
if self._missionBriefing == nil or self._missionBriefing == "" then self._missionBriefing = "No briefing available" end
local text = "Mission [" .. self.code .. "] ".. self.name .. "\n \n" .. self._missionBriefing .. " \n \n" .. stateString
trigger.action.outTextForGroup(groupId, text, 30);
end
---@param checkHealth boolean
---@param messageIfDone boolean
function Mission:UpdateState(checkHealth, messageIfDone)
if checkHealth == nil then checkHealth = false end
if messageIfDone == false then messageIfDone = true end
if self._state == "COMPLETED" then
return
end
if checkHealth == true then
local function unitAliveState(unitName)
local staticObject = StaticObject.getByName(unitName)
if staticObject then
if staticObject:isExist() == true then
local life0 = staticObject:getDesc().life
if staticObject:getLife() / life0 < 0.3 then
self._logger:debug("exploding unit")
trigger.action.explosion(staticObject:getPoint(), 100)
return false
end
return true
else
return false
end
else
local unit = Unit.getByName(unitName)
local alive = unit ~= nil and unit:isExist() == true
if alive == true then
if unit:getLife() / unit:getLife0() < 0.2 then
self._logger:debug("exploding unit")
trigger.action.explosion(unit:getPoint(), 100)
return false
end
return true
else
return false
end
end
end
for groupName, unitNameDict in pairs(self._missionGroups.unitsAlive) do
for unitName, isAlive in pairs(unitNameDict) do
if isAlive == true then
self._missionGroups.unitsAlive[groupName][unitName] = unitAliveState(unitName)
end
end
end
for groupName, unitNameDict in pairs(self._missionGroups.targetsAlive) do
for unitName, isAlive in pairs(unitNameDict) do
if isAlive == true then
self._missionGroups.targetsAlive[groupName][unitName] = unitAliveState(unitName)
end
end
end
end
if self._missionGroups.hasTargets == true then
local anyTargetAlive = function()
for _, units in pairs(self._missionGroups.targetsAlive) do
for _, isAlive in pairs(units) do
if isAlive == true then
return true
end
end
end
return false
end
if anyTargetAlive() ~= true then
self._state = "COMPLETED"
end
else
local function CountAliveGroups()
local aliveGroups = 0
for _, group in pairs(self._missionGroups.unitsAlive) do
local groupTotal = 0
local groupDeath = 0
for _, isAlive in pairs(group) do
if isAlive ~= true then
groupDeath = groupDeath + 1
end
groupTotal = groupTotal + 1
end
local aliveRatio = (groupTotal - groupDeath) / groupTotal
if aliveRatio >= MINIMAL_UNITS_ALIVE_RATIO then
aliveGroups = aliveGroups + 1
end
end
return aliveGroups
end
if CountAliveGroups() == 0 then
self._state = "COMPLETED"
end
end
---comment
---@param mission Mission
local NotifyMissionComplete = function(mission)
mission:NotifyMissionComplete()
return nil
end
if self._state == "COMPLETED" then
timer.scheduleFunction(NotifyMissionComplete, self, timer.getTime() + 3)
end
end
---private usage advised
function Mission:NotifyMissionComplete()
Spearhead.classes.stageClasses.helpers.MissionCommandsHelper.RemoveMissionToCommands(self)
self._logger:info("Mission Completed: " .. self._zoneName)
trigger.action.outText("Mission " .. self.name .. " [" .. self.code .. "] was completed succesfully" , 20)
for _, listener in pairs(self._completeListeners) do
pcall(function()
listener:OnMissionComplete(self)
end)
end
end
---@param listener MissionCompleteListener Object that implements "OnMissionComplete(self, mission)"
function Mission:AddMissionCompleteListener(listener)
if type(listener) ~= "table" then
return
end
table.insert(self._completeListeners, listener)
end
function Mission:OnUnitLost(object)
--[[
OnUnit lost event
]]--
self._logger:debug("Getting on unit lost event")
local category = Object.getCategory(object)
if category == Object.Category.UNIT then
local unitName = object:getName()
self._logger:debug("UnitName:" .. unitName)
local groupName = self._missionGroups.groupNamesPerunit[unitName]
self._missionGroups.unitsAlive[groupName][unitName] = false
if self._missionGroups.targetsAlive[groupName] and self._missionGroups.targetsAlive[groupName][unitName] then
self._missionGroups.targetsAlive[groupName][unitName] = false
end
elseif category == Object.Category.STATIC then
local name = object:getName()
self._missionGroups.unitsAlive[name][name] = false
self._logger:debug("Name " .. name)
if self._missionGroups.targetsAlive[name] and self._missionGroups.targetsAlive[name][name] then
self._missionGroups.targetsAlive[name][name] = false
end
end
self:UpdateState(false, true)
end
if not Spearhead.classes then Spearhead.classes = {} end
if not Spearhead.classes.stageClasses then Spearhead.classes.stageClasses = {} end
if not Spearhead.classes.stageClasses.Missions then Spearhead.classes.stageClasses.Missions = {} end
Spearhead.classes.stageClasses.Missions.Mission = Mission
@@ -0,0 +1,91 @@
---@class BlueSam
---@field Activate fun(self: BlueSam)
---@field private _database Database
---@field private _logger Logger
---@field private _zoneName string
---@field private _blueGroups Array<SpearheadGroup>
---@field private _cleanupUnits table<string, boolean>
local BlueSam = {}
function BlueSam.New(database, logger, zoneName)
BlueSam.__index = BlueSam
local self = setmetatable({}, BlueSam)
self._database = database
self._logger = logger
self._zoneName = zoneName
self._blueGroups = {}
self._cleanupUnits = {}
do
local groups = database:getBlueSamGroupsInZone(zoneName)
local blueUnitsPos = {}
local redUnitsPos = {}
for _, groupName in pairs(groups) do
local SpearheadGroup = Spearhead.classes.stageClasses.Groups.SpearheadGroup.New(groupName)
if SpearheadGroup then
if SpearheadGroup:GetCoalition() == 2 then
table.insert(self._blueGroups, SpearheadGroup)
end
for _, unit in pairs(SpearheadGroup:GetUnits()) do
if SpearheadGroup:GetCoalition() == 1 then
table.insert(blueUnitsPos, unit:getPoint())
elseif SpearheadGroup:GetCoalition() == 2 then
table.insert(redUnitsPos, unit:getPoint())
end
end
end
SpearheadGroup:Destroy()
end
do -- check cleanup requirements
-- Checks is any of the units are withing range (5m) of another unit.
-- If so, make sure to add them to the cleanup list.
local cleanup_distance = 5
for blueUnitName, blueUnitPos in pairs(blueUnitsPos) do
for redUnitName, redUnitPos in pairs(redUnitsPos) do
local distance = Spearhead.Util.VectorDistance(blueUnitPos, redUnitPos)
env.info("distance: " .. tostring(distance))
if distance <= cleanup_distance then
self._cleanupUnits[redUnitName] = true
end
end
end
end
end
return self
end
function BlueSam:Activate()
for unitName, needsCleanup in pairs(self._cleanupUnits) do
if needsCleanup == true then
Spearhead.DcsUtil.DestroyUnit(unitName)
else
local deathState = Spearhead.classes.persistence.Persistence.UnitDeadState(unitName)
if deathState and deathState.isDead == true then
Spearhead.DcsUtil.SpawnCorpse(deathState.country_id, unitName, deathState.type, deathState.pos, deathState.heading)
end
end
end
for _, group in pairs(self._blueGroups) do
group:Spawn()
end
end
if Spearhead == nil then Spearhead = {} end
if Spearhead.classes == nil then Spearhead.classes = {} end
if Spearhead.classes.stageClasses == nil then Spearhead.classes.stageClasses = {} end
if Spearhead.classes.stageClasses.SpecialZones == nil then Spearhead.classes.stageClasses.SpecialZones = {} end
Spearhead.classes.stageClasses.Missions.SpecialZones = BlueSam
@@ -0,0 +1,159 @@
---@class StageBase
---@field private _database Database
---@field private _logger Logger
---@field private _red_groups Array<SpearheadGroup>
---@field private _blue_groups Array<SpearheadGroup>
---@field private _cleanup_units table<string, boolean>
---@field private _airbase table?
---@field private _initialSide number?
local StageBase = {}
---comment
---@param databaseManager table
---@param logger table
---@param airbaseId integer
---@return StageBase
function StageBase.New(databaseManager, logger, airbaseId)
StageBase.__index = StageBase
local self = setmetatable({}, StageBase)
self._database = databaseManager
self._logger = logger
self._red_groups = {}
self._blue_groups = {}
self._cleanup_units = {}
self._airbase = Spearhead.DcsUtil.getAirbaseById(airbaseId)
self._initialSide = Spearhead.DcsUtil.getStartingCoalition(airbaseId)
do --init
local redUnitsPos = {}
local blueUnitsPos = {}
do -- fill tables
local redGroups = databaseManager:getRedGroupsAtAirbase(airbaseId)
if redGroups then
for _, groupName in pairs(redGroups) do
local shGroup = Spearhead.classes.stageClasses.Groups.SpearheadGroup.New(groupName)
table.insert(self._red_groups, shGroup)
for _, unit in shGroup:GetUnits() do
redUnitsPos[unit:getName()] = unit:getPoint()
end
shGroup:Destroy()
end
end
local blueGroups = databaseManager:getBlueGroupsAtAirbase(airbaseId)
if blueGroups then
for _, groupName in pairs(blueGroups) do
local shGroup = Spearhead.classes.stageClasses.Groups.SpearheadGroup.New(groupName)
table.insert(self._blue_groups, shGroup)
for _, unit in shGroup:GetUnits() do
blueUnitsPos[unit:getName()] = unit:getPoint()
end
shGroup:Destroy()
end
end
end
do -- check cleanup requirements
-- Checks is any of the units are withing range (5m) of another unit.
-- If so, make sure to add them to the cleanup list.
local cleanup_distance = 5
for blueUnitName, blueUnitPos in pairs(blueUnitsPos) do
for redUnitName, redUnitPos in pairs(redUnitsPos) do
local distance = Spearhead.Util.VectorDistance(blueUnitPos, redUnitPos)
env.info("distance: " .. tostring(distance))
if distance <= cleanup_distance then
self._cleanup_units[redUnitName] = true
end
end
end
end
end
return self
end
---@private
function StageBase:SpawnRedUnits()
---comment
---@param groups Array<SpearheadGroup>
local spawnAsync = function(groups)
for _, group in pairs(groups) do
group:Spawn()
end
return nil
end
timer.scheduleFunction(spawnAsync, self._red_groups, timer.getTime() + 3)
end
---@private
function StageBase:CleanRedUnits()
for _, value in pairs(self._red_groups) do
value:SpawnCorpsesOnly()
end
for _, unitName in pairs(self._cleanup_units) do
Spearhead.DcsUtil.DestroyUnit(unitName)
Spearhead.DcsUtil.CleanCorpse(unitName)
end
end
---@private
function StageBase:SpawnBlueUnits()
---comment
---@param groups Array<SpearheadGroup>
local spawnAsync = function(groups)
for _, group in pairs(groups) do
group:Spawn()
end
return nil
end
timer.scheduleFunction(spawnAsync, self._blue_groups, timer.getTime() + 3)
end
function StageBase:ActivateRedStage()
if self._initialSide == 2 and self._airbase then
self._airbase:setCoalition(1)
self._airbase:autoCapture(false)
end
self:SpawnRedUnits()
end
function StageBase:ActivateBlueStage()
if self._initialSide == 2 and self._airbase then
self._airbase:setCoalition(2)
end
self:CleanRedUnits()
self:SpawnBlueUnits()
end
if Spearhead == nil then Spearhead = {} end
if Spearhead.classes == nil then Spearhead.classes = {} end
if Spearhead.classes.stageClasses == nil then Spearhead.classes.stageClasses = {} end
if Spearhead.classes.stageClasses.SpecialZones == nil then Spearhead.classes.stageClasses.SpecialZones = {} end
Spearhead.classes.stageClasses.SpecialZones.StageBase = StageBase
-497
View File
@@ -1,497 +0,0 @@
local Stage = {}
do --init STAGE DIRECTOR
local stageDrawingId = 0
---comment
---@param stagezone_name string
---@param database table
---@param logger table
---@return table?
function Stage:new(stagezone_name, database, logger, stageConfig)
local o = {}
setmetatable(o, { __index = self })
o.zoneName = stagezone_name
local split = Spearhead.Util.split_string(stagezone_name, "_")
if Spearhead.Util.tableLength(split) < 2 then
Spearhead.AddMissionEditorWarning("Stage zone with name " .. stagezone_name .. " does not have a order number or valid format")
return nil
end
local orderNumber = tonumber(split[2])
if orderNumber == nil then
Spearhead.AddMissionEditorWarning("Stage zone with name " .. stagezone_name .. " does not have a valid order number : " .. split[2])
return nil
end
o.stageNumber = orderNumber
o.isActive = false
o.database = database
o.logger = logger
o.db = {}
o.db.missionsByCode = {}
o.db.missions = {}
o.db.sams = {}
o.db.redAirbasegroups = {}
o.db.blueAirbasegroups = {}
o.db.blueSamGroups = {}
o.db.airbaseIds = {}
o.db.farps = {}
o.activeStage = -99
o.preActivated = false
o.stageConfig = stageConfig or {}
o.stageDrawingId = stageDrawingId + 1
stageDrawingId = stageDrawingId + 1
do --Init Stage
logger:info("Initiating new Stage with name: " .. stagezone_name)
local missionZones = database:getMissionsForStage(stagezone_name)
for _, missionZone in pairs(missionZones) do
local mission = Spearhead.internal.Mission:new(missionZone, database, logger)
if mission then
o.db.missionsByCode[mission.code] = mission
if mission.missionType == Spearhead.internal.Mission.MissionType.SAM then
table.insert(o.db.sams, mission)
else
table.insert(o.db.missions, mission)
end
end
end
local randomMissionNames = database:getRandomMissionsForStage(stagezone_name)
local randomMissionByName = {}
for _, missionZoneName in pairs(randomMissionNames) do
local mission = Spearhead.internal.Mission:new(missionZoneName, database, logger)
if mission then
if randomMissionByName[mission.name] == nil then
randomMissionByName[mission.name] = {}
end
table.insert(randomMissionByName[mission.name], mission)
end
end
for _, missions in pairs(randomMissionByName) do
local mission = Spearhead.Util.randomFromList(missions)
if mission then
o.db.missionsByCode[mission.code] = mission
if mission.missionType == Spearhead.internal.Mission.MissionType.SAM then
table.insert(o.db.sams, mission)
else
table.insert(o.db.missions, mission)
end
end
end
local airbaseIds = database:getAirbaseIdsInStage(o.zoneName)
if airbaseIds ~= nil and type(airbaseIds) == "table" then
o.db.airbaseIds = airbaseIds
for _, airbaseId in pairs(airbaseIds) do
for _, groupName in pairs(database:getRedGroupsAtAirbase(airbaseId)) do
table.insert(o.db.redAirbasegroups, groupName)
Spearhead.DcsUtil.DestroyGroup(groupName)
end
for _, groupName in pairs(database:getBlueGroupsAtAirbase(airbaseId)) do
table.insert(o.db.blueAirbasegroups, groupName)
Spearhead.DcsUtil.DestroyGroup(groupName)
end
end
end
for _, samZoneName in pairs(database:getBlueSamsInStage(o.zoneName)) do
for _, samGroup in pairs(database:getBlueSamGroupsInZone(samZoneName)) do
table.insert(o.db.blueSamGroups, samGroup)
Spearhead.DcsUtil.DestroyGroup(samGroup)
end
end
local miscGroups = database:getMiscGroupsAtStage(o.zoneName)
for _, groupName in pairs(miscGroups) do
Spearhead.DcsUtil.DestroyGroup(groupName)
end
local farps = database:getFarpZonesInStage(o.zoneName)
if farps ~= nil and type(farps) == "table" then o.db.farps = farps end
end
o.StageCompleteListeners = {}
---comment
---@param self table
---@param StageCompleteListener table an Object with function onStageCompleted(stage)
o.AddStageCompleteListener = function(self, StageCompleteListener)
if type(StageCompleteListener) ~= "table" then
return
end
table.insert(self.StageCompleteListeners, StageCompleteListener)
end
local triggerStageCompleteListeners = function(self)
self.isActive = false
for _, callable in pairs(self.StageCompleteListeners) do
local succ, err = pcall( function()
callable:onStageCompleted(self)
end)
if err then
self.logger:warn("Error in misstion complete listener:" .. err)
end
end
end
o.IsComplete = function(self)
for i, mission in pairs(self.db.missions) do
local state = mission:GetState()
if state == Spearhead.internal.Mission.MissionState.ACTIVE or state == Spearhead.internal.Mission.MissionState.NEW then
return false
end
end
return true
end
local CheckContinuousAsync = function(self, time)
self.logger:info("Checking stage completion for stage: " .. self.zoneName)
if self.activeStage == self.stageNumber then
return nil -- stop looping if this stage is not even active
end
if self:IsComplete() == true then
triggerStageCompleteListeners(self)
return nil
end
return time + 60
end
---Activates all SAMS, Airbase units etc all at once.
---@param self table
o.PreActivate = function(self)
if self.preActivated == false then
self.preActivated = true
for key, mission in pairs(self.db.sams) do
if mission and mission.Activate then
mission:Activate()
end
end
self.logger:debug("Pre-activating stage with airbase groups amount: " .. Spearhead.Util.tableLength(self.db.redAirbasegroups))
for _ , groupName in pairs(self.db.redAirbasegroups) do
Spearhead.DcsUtil.SpawnGroupTemplate(groupName)
end
end
if self.activeStage == self.stageNumber then
for _, mission in pairs(self.db.sams) do
self:AddCommmandsForMissionToAllPlayers(mission)
end
end
end
local activateMissionsIfApplicableAsync = function(self)
self:ActivateMissionsIfApplicable(self)
end
o.MarkStage = function(self, blue)
local fillColor = {1, 0, 0, 0.1}
local line ={ 1, 0,0, 1 }
if blue == true then
fillColor = {0, 0, 1, 0.1}
line ={ 0, 0,1, 1 }
end
local zone = Spearhead.DcsUtil.getZoneByName(self.zoneName)
if zone and self.stageConfig:isDrawStagesEnabled() == true then
self.logger:debug("drawing stage")
if zone.zone_type == Spearhead.DcsUtil.ZoneType.Cilinder then
trigger.action.circleToAll(-1, self.stageDrawingId, {x = zone.x, y = 0 , z = zone.z}, zone.radius, {0,0,0,0}, {0,0,0,0},4, true)
else
--trigger.action.circleToAll(-1, self.stageDrawingId, {x = zone.x, y = 0 , z = zone.z}, zone.radius, { 1, 0,0, 1 }, {1,0,0,1},4, true)
trigger.action.quadToAll( -1, self.stageDrawingId, zone.verts[1], zone.verts[2], zone.verts[3], zone.verts[4], {0,0,0,0}, {0,0,0,0}, 4, true)
end
trigger.action.setMarkupColorFill(self.stageDrawingId, fillColor)
trigger.action.setMarkupColor(self.stageDrawingId, line)
end
end
o.ActivateStage = function(self)
self.isActive = true;
pcall(function()
self:MarkStage()
end)
self:PreActivate()
local miscGroups = self.database:getMiscGroupsAtStage(self.zoneName)
self.logger:debug("Activating Misc groups for zone: " .. Spearhead.Util.tableLength(miscGroups))
for _, groupName in pairs(miscGroups) do
Spearhead.DcsUtil.SpawnGroupTemplate(groupName)
end
for _, mission in pairs(self.db.missions) do
if mission.missionType == Spearhead.internal.Mission.MissionType.DEAD then
mission:Activate()
self:AddCommmandsForMissionToAllPlayers(mission)
end
end
timer.scheduleFunction(activateMissionsIfApplicableAsync, self, timer.getTime() + 5)
timer.scheduleFunction(CheckContinuousAsync, self, timer.getTime() + 60)
end
o.ActivateMissionsIfApplicable = function (self)
local activeCount = 0
local availableMissions = {}
for _, mission in pairs(self.db.missionsByCode) do
local state = mission:GetState()
if state == Spearhead.internal.Mission.MissionState.ACTIVE then
activeCount = activeCount + 1
end
if state == Spearhead.internal.Mission.MissionState.NEW then
table.insert(availableMissions, mission)
end
end
local max = self.stageConfig:getMaxMissionsPerStage() or 10
local availableMissionsCount = Spearhead.Util.tableLength(availableMissions)
if activeCount < max and availableMissionsCount > 0 then
for i = activeCount+1, max do
if availableMissionsCount == 0 then
i = max+1 --exits this loop
else
local index = math.random(1, availableMissionsCount)
local mission = table.remove(availableMissions, index)
if mission then
mission:Activate()
self:AddCommmandsForMissionToAllPlayers(mission)
activeCount = activeCount + 1;
end
availableMissionsCount = availableMissionsCount - 1
end
end
end
end
---Cleans up all missions
---@param self table
o.Clean = function(self)
for key, mission in pairs(self.db.missions) do
mission:Cleanup()
end
for key, samMission in pairs(self.db.sams) do
samMission:Cleanup()
end
for _, airbase in pairs(self.db.airbases) do
for _, redGroupName in pairs(airbase.redAirbaseGroupNames) do
Spearhead.DcsUtil.DcsUtil.DestroyGroup(redGroupName)
end
end
logger:debug("'" .. Spearhead.Util.toString(self.zoneName) .. "' cleaned")
end
local ActivateBlueAsync = function(self)
pcall(function()
self:MarkStage(true)
end)
for _, blueSamGroupName in pairs(self.db.blueSamGroups) do
Spearhead.DcsUtil.SpawnGroupTemplate(blueSamGroupName)
end
for key, airbaseId in pairs(self.db.airbaseIds) do
local airbase = Spearhead.DcsUtil.getAirbaseById(airbaseId)
if airbase then
local startingCoalition = Spearhead.DcsUtil.getStartingCoalition(airbaseId)
if startingCoalition == coalition.side.BLUE then
airbase:setCoalition(2)
for _, blueGroupName in pairs(self.db.blueAirbasegroups) do
Spearhead.DcsUtil.SpawnGroupTemplate(blueGroupName)
end
else
airbase:setCoalition(0)
end
end
end
return nil
end
---Sets airfields to blue and spawns friendly farps
o.ActivateBlueStage = function(self)
logger:debug("Setting stage '" .. Spearhead.Util.toString(self.zoneName) .. "' to blue")
for _, groupName in pairs(self.db.redAirbasegroups) do
Spearhead.DcsUtil.DestroyGroup(groupName)
end
for _, mission in pairs(self.db.missions) do
mission:Cleanup()
end
for _, mission in pairs(self.db.sams) do
mission:Cleanup()
end
timer.scheduleFunction(ActivateBlueAsync, self, timer.getTime() + 3)
end
o.OnStatusRequestReceived = function(self, groupId)
if self.activeStage ~= self.stageNumber then
return
end
trigger.action.outTextForGroup(groupId, "Status Update incoming... ", 3)
local text = "Mission Status: \n"
local totalmissions = 0
local completedMissions = 0
for _, mission in pairs(self.db.missionsByCode) do
totalmissions = totalmissions + 1
if mission.missionState == Spearhead.internal.Mission.MissionState.ACTIVE then
text = text .. "\n [" .. mission.code .. "] " .. mission.name ..
" (" .. mission.missionTypeDisplayName .. ") \n"
end
if mission.missionState == Spearhead.internal.Mission.MissionState.COMPLETED then
completedMissions = completedMissions + 1
end
end
local completionPercentage = math.floor((completedMissions / totalmissions) * 100)
text = text .. " \n Missions Complete: " .. completionPercentage .. "%"
self.logger:debug(text)
trigger.action.outTextForGroup(groupId, text, 20)
end
o.OnStageNumberChanged = function (self, number)
if self.activeStage == number then --only activate once for a stage
return
end
local previousActive = self.activeStage
self.activeStage = number
if Spearhead.capInfo.IsCapActiveWhenZoneIsActive(self.zoneName, number) == true then
self:PreActivate()
end
if number == self.stageNumber then
self:ActivateStage()
end
if previousActive <= self.stageNumber then
if number > self.stageNumber then
self:ActivateBlueStage()
self:RemoveAllMissionCommands()
end
end
end
--- input = { self, groupId, missionCode }
local ShowBriefingClicked = function (input)
local self = input.self
local groupId = input.groupId
local missionCode = input.missionCode
local mission = self.db.missionsByCode[missionCode]
if mission then
mission:ShowBriefing(groupId)
end
end
o.RemoveMissionCommands = function (self, mission)
self.logger:debug("Removing commands for: " .. mission.name)
local folderName = mission.name .. "(" .. mission.missionTypeDisplayName .. ")"
for i = 0, 2 do
local players = coalition.getPlayers(i)
for _, playerUnit in pairs(players) do
local groupId = playerUnit:getGroup():getID()
missionCommands.removeItemForGroup(groupId, { "Missions", folderName })
end
end
end
o.RemoveAllMissionCommands = function (self)
for _, mission in pairs(self.db.missionsByCode) do
self:RemoveMissionCommands(mission)
end
end
o.AddCommandsForMissionToGroup = function (self, groupId, mission)
local folderName = mission.name .. "(" .. mission.missionTypeDisplayName .. ")"
missionCommands.addSubMenuForGroup(groupId, folderName, { "Missions"} )
missionCommands.addCommandForGroup(groupId, "Show Briefing", { "Missions", folderName }, ShowBriefingClicked, { self = self, groupId = groupId, missionCode = mission.code })
end
o.AddCommmandsForMissionToAllPlayers = function(self, mission)
for i = 0, 2 do
local players = coalition.getPlayers(i)
for _, playerUnit in pairs(players) do
local groupId = playerUnit:getGroup():getID()
self:AddCommandsForMissionToGroup(groupId, mission)
end
end
end
o.OnPlayerEntersUnit = function (self, unit)
if self.activeStage == self.stageNumber then
local groupId = unit:getGroup():getID()
for _, mission in pairs(self.db.missionsByCode) do
if mission.missionState == Spearhead.internal.Mission.MissionState.ACTIVE then
self:AddCommandsForMissionToGroup(groupId, mission)
end
end
end
end
local removeMissionCommandsDelayed = function(input)
local self = input.self
local mission = input.mission
self:RemoveMissionCommands(mission)
end
o.OnMissionComplete = function(self, mission)
timer.scheduleFunction(removeMissionCommandsDelayed, { self = self, mission = mission}, timer.getTime() + 20)
if(self:IsComplete()) then
timer.scheduleFunction(triggerStageCompleteListeners, self, timer.getTime() + 15)
else
timer.scheduleFunction(activateMissionsIfApplicableAsync, self, timer.getTime() + 10)
end
end
for _, mission in pairs(o.db.missionsByCode) do
mission:AddMissionCompleteListener(o)
end
Spearhead.Events.AddOnPlayerEnterUnitListener(o)
Spearhead.Events.AddOnStatusRequestReceivedListener(o)
Spearhead.Events.AddStageNumberChangedListener(o)
return o
end
end
if not Spearhead.internal then Spearhead.internal = {} end
Spearhead.internal.Stage = Stage
@@ -0,0 +1,438 @@
---@alias StageColor
---| "RED"
---| "BLUE"
---| "GRAY"
--- @class StageData
--- @field missionsByCode table<string, Mission>
--- @field missions Array<Mission>
--- @field sams Array<Mission>
--- @field blueSams Array<BlueSam>
--- @field airbases Array<StageBase>
--- @field miscGroups Array<SpearheadGroup>
--- @field maxMissions integer
--- @class StageInitData
--- @field stageZoneName string
--- @field stageNumber integer
--- @field stageDisplayName string
--- @class StageCompleteListener
--- @field OnStageComplete fun(self:StageCompleteListener, stage:Stage)
--- @class Stage : MissionCompleteListener, OnStageChangedListener
--- @field zoneName string
--- @field stageName string
--- @field stageNumber number
--- @field protected _isActive boolean
--- @field protected _isComplete boolean
--- @field protected _missionPriority MissionPriority
--- @field protected _database Database
--- @field protected _db StageData
--- @field protected _logger Logger
--- @field protected _preActivated boolean
--- @field protected _activeStage integer
--- @field protected _stageConfig StageConfig
--- @field protected _stageDrawingId integer
--- @field protected _spawnedGroups Array<string>
--- @field protected _stageCompleteListeners Array<StageCompleteListener>
--- @field protected CheckContinuousAsync fun(self:Stage, time:number) : number?
--- @field protected OnPostStageComplete fun(self:Stage)?
--- @field protected OnPostBlueActivated fun(self:Stage)?
local Stage = {}
local stageDrawingId = 100
---comment
---@param database Database
---@param stageConfig StageConfig
---@param logger any
---@param initData StageInitData
---@param missionPriority MissionPriority
---@return Stage
function Stage.New(database, stageConfig, logger, initData, missionPriority)
local SpearheadGroup = Spearhead.classes.stageClasses.Groups.SpearheadGroup
Stage.__index = Stage
local o = {}
local self = setmetatable(o, Stage)
self.zoneName = initData.stageZoneName
self.stageNumber = initData.stageNumber
self._isActive = false
self._isComplete = false
self.stageName = initData.stageDisplayName
self.OnPostStageComplete = nil
self.OnPostBlueActivated = nil
self._database = database
self._logger = logger
self._db = {
missionsByCode = {},
missions = {},
sams ={},
blueSams = {},
airbases ={},
miscGroups = {},
maxMissions = stageConfig.maxMissionsPerStage
}
self._activeStage = -99
self._preActivated = false
self._stageConfig = stageConfig or {}
self._stageDrawingId = stageDrawingId + 1
self._spawnedGroups = {}
self._missionPriority = missionPriority
self._stageCompleteListeners = {}
stageDrawingId = stageDrawingId + 1
self._logger:info("Initiating new Stage with name: " .. self.zoneName)
---comment
---@param self Stage
---@param time number?
self.CheckContinuousAsync = function (self, time)
self:CheckAndUpdateSelf()
if self:IsComplete() == true then
self:NotifyComplete()
return nil
end
return time + 20
end
do -- load tables
local missionZones = database:getMissionsForStage(self.zoneName)
for _, missionZone in pairs(missionZones) do
local mission = Spearhead.classes.stageClasses.Missions.Mission.New(missionZone, self._missionPriority, database, logger)
if mission then
self._db.missionsByCode[mission.code] = mission
if mission.missionType == "SAM" then
table.insert(self._db.sams, mission)
else
table.insert(self._db.missions, mission)
end
end
end
local randomMissionNames = database:getRandomMissionsForStage(self.zoneName)
local randomMissionByName = {}
for _, missionZoneName in pairs(randomMissionNames) do
local mission = Spearhead.classes.stageClasses.Missions.Mission.New(missionZoneName, "primary", database, logger)
if mission then
if randomMissionByName[mission.name] == nil then
randomMissionByName[mission.name] = {}
end
table.insert(randomMissionByName[mission.name], mission)
end
end
for _, missions in pairs(randomMissionByName) do
local mission = Spearhead.Util.randomFromList(missions)
if mission then
self._db.missionsByCode[mission.code] = mission
if mission.missionType == "SAM" then
table.insert(self._db.sams, mission)
else
table.insert(self._db.missions, mission)
end
end
end
for _, mission in pairs(self._db.missionsByCode) do
mission:AddMissionCompleteListener(self)
end
local airbaseIds = database:getAirbaseIdsInStage(self.zoneName)
if airbaseIds ~= nil and type(airbaseIds) == "table" then
for _, airbaseId in pairs(airbaseIds) do
local airbase = Spearhead.classes.stageClasses.SpecialZones.StageBase.New(database, logger, airbaseId)
table.insert(self._db.airbases, airbase)
end
end
for _, samZoneName in pairs(database:getBlueSamsInStage(self.zoneName)) do
local blueSam = Spearhead.classes.stageClasses.SpecialZones.BlueSam:new(database, logger, samZoneName)
table.insert(self._db.blueSams, blueSam)
end
local miscGroups = database:getMiscGroupsAtStage(self.zoneName)
for _, groupName in pairs(miscGroups) do
local miscGroup = SpearheadGroup.New(groupName)
table.insert(self._db.miscGroups, miscGroup)
Spearhead.DcsUtil.DestroyGroup(groupName)
end
end
Spearhead.Events.AddStageNumberChangedListener(self)
return self
end
---@return boolean
function Stage:IsComplete()
if self._isComplete == true then return true end
for i, mission in pairs(self._db.sams) do
local state = mission:GetState()
if state == "ACTIVE" or state == "NEW" then
return false
end
end
for i, mission in pairs(self._db.missions) do
local state = mission:GetState()
if state == "ACTIVE" or state == "NEW" then
return false
end
end
self._isComplete = true
return true
end
---@return boolean
function Stage:IsActive()
return self._isActive == true
end
---comment
function Stage:CheckAndUpdateSelf()
self._logger:debug("Checking on Stage: " .. self.zoneName)
local activeCount = 0
local dbTables = self:GetStageTables()
local availableMissions = {}
for _, mission in pairs(dbTables.missionsByCode) do
local state = mission:GetState()
if state == "ACTIVE" then
activeCount = activeCount + 1
end
if state == "NEW" then
table.insert(availableMissions, mission)
end
end
local max = dbTables.maxMissions
local availableMissionsCount = Spearhead.Util.tableLength(availableMissions)
if activeCount < max and availableMissionsCount > 0 then
for i = activeCount+1, max do
if availableMissionsCount == 0 then
i = max+1 --exits this loop
else
local index = math.random(1, availableMissionsCount)
---@type Mission
local mission = table.remove(availableMissions, index)
if mission then
mission:SpawnActive()
activeCount = activeCount + 1;
end
availableMissionsCount = availableMissionsCount - 1
end
end
end
end
---private use only
function Stage:NotifyComplete()
self._logger:info("Stage complete: " .. self.stageName)
for _, listener in pairs(self._stageCompleteListeners) do
pcall(function()
listener:OnStageComplete(self)
end)
end
if self.OnPostStageComplete then
timer.scheduleFunction(self.OnPostStageComplete, self, timer.getTime() + 3)
end
end
---@param listener StageCompleteListener
function Stage:AddStageCompleteListener(listener)
table.insert(self._stageCompleteListeners, listener)
end
---Activates all SAMS, Airbase units etc all at once.
function Stage:PreActivate()
if self._preActivated == false then
self._preActivated = true
for key, mission in pairs(self._db.sams) do
if mission then
mission:SpawnActive()
end
end
for _, airbase in pairs(self._db.airbases) do
airbase:ActivateRedStage()
end
end
end
---@param stageColor StageColor
function Stage:MarkStage(stageColor)
local fillColor = {1, 0, 0, 0.1}
local line ={ 1, 0,0, 1 }
if stageColor == "RED" then
fillColor = {1, 0, 0, 0.1}
line ={ 1, 0,0, 1 }
elseif stageColor =="BLUE" then
fillColor = {0, 0, 1, 0.1}
line ={ 0, 0,1, 1 }
elseif stageColor == "GRAY" then
fillColor = {80/255, 80/255, 80/255, 0.15}
line ={ 80/255, 80/255,80/255, 1 }
end
local zone = Spearhead.DcsUtil.getZoneByName(self.zoneName)
if zone and self._stageConfig.isDrawStagesEnabled == true then
self._logger:debug("drawing stage: " .. self.zoneName)
if zone.zone_type == Spearhead.DcsUtil.ZoneType.Cilinder then
trigger.action.circleToAll(-1, self._stageDrawingId, {x = zone.x, y = 0 , z = zone.z}, zone.radius, {0,0,0,0}, {0,0,0,0},4, true)
else
--trigger.action.circleToAll(-1, self.stageDrawingId, {x = zone.x, y = 0 , z = zone.z}, zone.radius, { 1, 0,0, 1 }, {1,0,0,1},4, true)
trigger.action.quadToAll( -1, self._stageDrawingId, zone.verts[1], zone.verts[2], zone.verts[3], zone.verts[4], {0,0,0,0}, {0,0,0,0}, 4, true)
end
trigger.action.setMarkupColorFill(self._stageDrawingId, fillColor)
trigger.action.setMarkupColor(self._stageDrawingId, line)
end
end
function Stage:ActivateStage()
self._isActive = true;
pcall(function()
self:MarkStage("RED")
end)
self:PreActivate()
self._logger:debug("Activating Misc groups for zone. Count: " .. Spearhead.Util.tableLength(self._db.miscGroups))
for _, miscGroup in pairs(self._db.miscGroups) do
miscGroup:Spawn()
end
for _, mission in pairs(self._db.missions) do
if mission.missionType == "DEAD" then
mission:SpawnActive()
end
end
timer.scheduleFunction(self.CheckContinuousAsync, self, timer.getTime() + 3)
end
---Private usage only
---@return StageData
function Stage:GetStageTables()
return self._db
end
---comment
---@param self Stage
---@param number integer
function Stage:OnStageNumberChanged(number)
if self._activeStage == number then --only activate once for a stage
return
end
local previousActive = self._activeStage
self._activeStage = number
if Spearhead.capInfo.IsCapActiveWhenZoneIsActive(self.zoneName, number) == true then
self:PreActivate()
end
if number == self.stageNumber then
self:ActivateStage()
end
if previousActive <= self.stageNumber then
if number > self.stageNumber then
self:ActivateBlueStage()
end
end
end
function Stage:GetBriefing()
return "Briefing For "
end
---@param self Stage
---@param mission Mission
Stage.OnMissionComplete = function(self, mission)
self:CheckAndUpdateSelf()
end
---private use only
function Stage:ActivateBlueGroups()
for _, blueSam in pairs(self._db.blueSams) do
blueSam:Activate()
end
for _, airbase in pairs(self._db.airbases) do
airbase:ActivateBlueStage()
end
if self.OnPostBlueActivated then
pcall(function()
self:OnPostBlueActivated()
end)
end
end
function Stage:ActivateBlueStage()
self._logger:debug("Setting stage '" .. Spearhead.Util.toString(self.zoneName) .. "' to blue")
for _, mission in pairs(self._db.missions) do
mission:SpawnPersistedState()
end
for _, mission in pairs(self._db.sams) do
mission:SpawnPersistedState()
end
for _, miscGroup in pairs(self._db.miscGroups) do
miscGroup:Spawn()
end
---@param self Stage
local ActivateBlueAsync = function(self)
pcall(function()
self:MarkStage("BLUE")
end)
self:ActivateBlueGroups()
return nil
end
timer.scheduleFunction(ActivateBlueAsync, self, timer.getTime() + 3)
end
if not Spearhead.classes then Spearhead.classes = {} end
if not Spearhead.classes.stageClasses then Spearhead.classes.stageClasses = {} end
if not Spearhead.classes.stageClasses.Stages then Spearhead.classes.stageClasses.Stages = {} end
if not Spearhead.classes.stageClasses.Stages.BaseStage then Spearhead.classes.stageClasses.Stages.BaseStage = {} end
Spearhead.classes.stageClasses.Stages.BaseStage.Stage = Stage
@@ -0,0 +1,58 @@
---@class ExtraStage : Stage
local ExtraStage = {}
---comment
---@param database Database
---@param stageConfig StageConfig
---@param logger any
---@param initData StageInitData
---@return ExtraStage
function ExtraStage.New(database, stageConfig, logger, initData)
-- "Import"
local Stage = Spearhead.classes.stageClasses.Stages.BaseStage.Stage
setmetatable(ExtraStage, Stage)
ExtraStage.__index = ExtraStage
local self = Stage.New(database, stageConfig, logger, initData, "secondary") --[[@as ExtraStage]]
setmetatable(self, ExtraStage)
self.OnPostBlueActivated = function (selfStage)
selfStage:MarkStage("GRAY")
end
self.OnPostStageComplete = function (selfStage)
self:ActivateBlueStage()
end
return self
end
---comment
---@param self Stage
---@param number integer
function ExtraStage:OnStageNumberChanged(number)
self._activeStage = number
if Spearhead.capInfo.IsCapActiveWhenZoneIsActive(self.zoneName, number) == true then
self:PreActivate()
end
if number == self.stageNumber then
self:ActivateStage()
end
if self._isComplete == true then
self:ActivateBlueStage()
end
end
if not Spearhead.classes then Spearhead.classes = {} end
if not Spearhead.classes.stageClasses then Spearhead.classes.stageClasses = {} end
if not Spearhead.classes.stageClasses.Stages then Spearhead.classes.stageClasses.Stages = {} end
Spearhead.classes.stageClasses.Stages.ExtraStage = ExtraStage
@@ -0,0 +1,28 @@
---@class PrimaryStage : Stage
local PrimaryStage = {}
---comment
---@param database Database
---@param stageConfig StageConfig
---@param logger any
---@param initData StageInitData
---@return PrimaryStage
function PrimaryStage.New(database, stageConfig, logger, initData)
-- "Import"
local Stage = Spearhead.classes.stageClasses.Stages.BaseStage.Stage
setmetatable(PrimaryStage, Stage)
PrimaryStage.__index = PrimaryStage
setmetatable(PrimaryStage, {__index = Stage})
local o = Stage.New(database, stageConfig, logger, initData, "primary") --[[@as PrimaryStage]]
return o
end
if not Spearhead.classes then Spearhead.classes = {} end
if not Spearhead.classes.stageClasses then Spearhead.classes.stageClasses = {} end
if not Spearhead.classes.stageClasses.Stages then Spearhead.classes.stageClasses.Stages = {} end
Spearhead.classes.stageClasses.Stages.PrimaryStage = PrimaryStage
@@ -0,0 +1,77 @@
---@class WaitingStage : Stage
---@field private _waitTimeSeconds integer
---@field private _startTime number
local WaitingStage = {}
---@class WaitingStageInitData : StageInitData
---@field waitingSeconds integer
local WaitingStageInitData = {}
---comment
---@param database Database
---@param stageConfig StageConfig
---@param logger any
---@param initData WaitingStageInitData
---@return WaitingStage
function WaitingStage.New(database, stageConfig, logger, initData)
-- "Import"
local Stage = Spearhead.classes.stageClasses.Stages.BaseStage.Stage
setmetatable(WaitingStage, Stage)
WaitingStage.__index = WaitingStage
setmetatable(WaitingStage, {__index = Stage})
local self = Stage.New(database, stageConfig, logger, initData, "primary") --[[@as WaitingStage]]
setmetatable(self, WaitingStage)
self._waitTimeSeconds = 5
if initData.waitingSeconds and initData.waitingSeconds > 5 then self._waitTimeSeconds = initData.waitingSeconds end
self._startTime = nil
self.CheckContinuousAsync = function (self, time)
if self:IsComplete() then
self:NotifyComplete()
return nil
end
return time + 2
end
return self
end
function WaitingStage:ActivateStage()
self._logger:info("Starting Waiting Stage '" .. self.zoneName .. "' which will complete in about " .. self._waitTimeSeconds .. " seconds")
self._isActive = true
self._startTime = timer.getTime()
timer.scheduleFunction(self.CheckContinuousAsync, self, self._startTime + self._waitTimeSeconds)
end
function WaitingStage:IsComplete()
if timer.getTime() > (self._startTime + self._waitTimeSeconds) then return true end
return false
end
function WaitingStage:OnStageNumberChanged()
self._logger:debug("Waiting Stage OnStageNumberChanged override")
end
function WaitingStage:MarkStage(stageColor)
self._logger:debug("Waiting Stage MarkStage override")
end
function WaitingStage:GetExpectedTime()
return self._startTime + self._waitTimeSeconds
end
if not Spearhead.classes then Spearhead.classes = {} end
if not Spearhead.classes.stageClasses then Spearhead.classes.stageClasses = {} end
if not Spearhead.classes.stageClasses.Stages then Spearhead.classes.stageClasses.Stages = {} end
Spearhead.classes.stageClasses.Stages.WaitingStage = WaitingStage
@@ -0,0 +1,135 @@
local MissionCommandsHelper = {}
do
---@type table<string, Mission>
local missionsByCode = {}
---@type table<string, boolean>
local enabledByCode = {}
local updateNeeded = false
---Add a mission to the F10 commands menu
---@param mission Mission
MissionCommandsHelper.AddMissionToCommands = function (mission)
missionsByCode[tostring(mission.code)] = mission
enabledByCode[tostring(mission.code)] = true
updateNeeded = true
end
---Removes a mission from the F10 commands menu
---@param mission Mission
MissionCommandsHelper.RemoveMissionToCommands = function (mission)
enabledByCode[tostring(mission.code)] = false
updateNeeded = true
end
local folderNames = {
primary = "Primary Missions",
secondary = "Secondary Missions"
}
---Add Base Folder
---@param groupId integer
local addMissionFolders = function(groupId)
missionCommands.addSubMenuForGroup(groupId, folderNames.primary)
missionCommands.addSubMenuForGroup(groupId, folderNames.secondary)
end
---Add Mission Folder
---@param groupId integer
local removeMissionFolders = function(groupId)
missionCommands.removeItemForGroup(groupId , { folderNames.primary } )
missionCommands.removeItemForGroup(groupId , { folderNames.secondary } )
end
local missionBriefingRequested = function(args)
---@type Mission
local mission = args.mission
local groupID = args.groupId
mission:ShowBriefing(groupID)
end
---comment
---@param groupId integer
---@param mission Mission
local addMissionCommands = function(groupId, mission)
local path = nil
if mission.priority == "primary" then
path = { [1] = folderNames.primary }
elseif mission.priority == "secondary" then
path = { [1] = folderNames.secondary }
end
if path then
local missionFolderName = "[" .. mission.code .. "]" .. mission.name
missionCommands.addSubMenuForGroup(groupId, missionFolderName, path)
table.insert(path, missionFolderName)
missionCommands.addCommandForGroup(groupId, "Briefing" , path , missionBriefingRequested, { groupId = groupId, mission = mission })
end
end
local updateCommandsForGroup = function(group)
local groupID = group:getID()
-- Cleanup mission folder
removeMissionFolders(groupID)
-- Add mission folders
addMissionFolders(groupID)
for code, enabled in pairs(enabledByCode) do
if enabled == true then
local mission = missionsByCode[code]
if mission then
addMissionCommands(groupID, mission)
end
end
end
end
local UpdateContinuous = function(none, time)
if updateNeeded == false then
return time + 15
end
for _, unit in pairs(Spearhead.DcsUtil.getAllPlayerUnits()) do
if unit and unit:isExist() then
local group = unit:getGroup()
if group then
updateCommandsForGroup(group)
end
end
end
updateNeeded = false
return time + 15
end
timer.scheduleFunction(UpdateContinuous, {}, timer.getTime() + 10)
do -- Player enter unit listener
local onPlayerEnterUnit = function(unit)
if unit then
local group = unit:getGroup()
if group then updateCommandsForGroup(group) end
end
end
local OnPlayerEnterUnitListener = {
OnPlayerEntersUnit = function (self, unit)
onPlayerEnterUnit(unit)
end,
}
Spearhead.Events.AddOnPlayerEnterUnitListener(OnPlayerEnterUnitListener)
end
end
if Spearhead == nil then Spearhead = {} end
if Spearhead.classes == nil then Spearhead.classes = {} end
if Spearhead.classes.stageClasses == nil then Spearhead.classes.stageClasses = {} end
if Spearhead.classes.stageClasses.helpers == nil then Spearhead.classes.stageClasses.helpers = {} end
Spearhead.classes.stageClasses.helpers.MissionCommandsHelper = MissionCommandsHelper