* 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
+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