added lua check CI step (#49)
Publish Release / build (push) Failing after 3s

Reviewed-on: #49
Co-authored-by: dutchie031 <timrorije@gmail.com>
This commit was merged in pull request #49.
This commit is contained in:
2026-09-20 16:08:04 +00:00
committed by dutchie031
parent 9b2540d461
commit 9549e8c48e
61 changed files with 1058 additions and 881 deletions
+115 -100
View File
@@ -2,7 +2,6 @@ local Events = require("classes.spearhead_events")
local Util = require("classes.util.Util")
local DcsUtil = require("classes.util.DcsUtil")
local Logger = require("classes.util.Logger")
local MissionEditorWarnings = require("classes.util.MissionEditorWarnings")
local PersistenceConfig = require("classes.configuration.PersistenceConfig")
local ExtraStage = require("classes.stageClasses.Stages.ExtraStage")
local PrimaryStage = require("classes.stageClasses.Stages.PrimaryStage")
@@ -35,7 +34,6 @@ local singletonInstance = nil
---@param spawnManager SpawnManager
---@return GlobalStageManager
function GlobalStageManager.new(database, stageConfig, logLevel, spawnManager)
if singletonInstance ~= nil then
return singletonInstance
end
@@ -44,7 +42,7 @@ function GlobalStageManager.new(database, stageConfig, logLevel, spawnManager)
logger:info("Using Stage Log Level: " .. logLevel)
local self = setmetatable({}, GlobalStageManager)
singletonInstance = self
self.database = database
self.stageConfig = stageConfig
self._missionCommandsHelper = MissionCommandsHelper.getOrCreate()
@@ -52,14 +50,15 @@ function GlobalStageManager.new(database, stageConfig, logLevel, spawnManager)
self.logger = logger
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")
logger:warn(
"Spearhead will not automatically progress stages due to the given settings. If you manually have implemented this, please ignore this message")
end
Events.AddStageNumberChangedListener(self)
for _, stageName in pairs(database:getStagezoneNames()) do
logger:debug("Found stage zone with name: " .. stageName)
local parseResult = self:ParseStageName(stageName)
if parseResult.isValid == false then
logger:warn("Stage zone with name " .. stageName .. " is not valid: " .. parseResult.invalidReason)
@@ -81,12 +80,12 @@ function GlobalStageManager.new(database, stageConfig, logLevel, spawnManager)
stage:AddStageCompleteListener(self)
self._stageRepository:AddStage(stage)
elseif parseResult.stageType == "WaitingStage" then
local waitingStage = WaitingStage.New(database, stageConfig, logger, initData, parseResult.waitingStageSeconds, spawnManager)
local waitingStage = WaitingStage.New(database, stageConfig, logger, initData,
parseResult.waitingStageSeconds, spawnManager)
waitingStage:AddStageCompleteListener(self)
self._stageRepository:AddStage(waitingStage)
end
end
end
singletonInstance = self
@@ -103,11 +102,11 @@ function GlobalStageManager:Start()
local stageLanes = self._stageRepository:getAllStageLanes()
for _, stageLane in pairs(stageLanes) do
local stageLaneIdentifier = stageLane:GetStageLaneIdentifier()
local persistedStage = Persistence.GetActiveStage(stageLaneIdentifier)
if persistedStage then
self.logger:info("Loaded persisted stage " .. persistedStage .. " for lane " .. (stageLaneIdentifier or "default"))
self.logger:info("Loaded persisted stage " ..
persistedStage .. " for lane " .. (stageLaneIdentifier or "default"))
stageLane:SetActiveStageIndex(persistedStage)
Events.PublishStageNumberChanged(persistedStage, stageLaneIdentifier)
else
@@ -139,17 +138,16 @@ end
---@param stageName string
---@return StageNameParseResult
function GlobalStageManager:ParseStageName(stageName)
local split = Util.split_string(stageName, "_")
if Util.tableLength(split) < 3 then
return { isValid = false, invalidReason = "Stage zone with name " .. stageName .. " does not have a order number or valid format" }
return { isValid = false, invalidReason = "Stage zone with name " ..
stageName .. " does not have a order number or valid format" }
end
local typePart = string.lower(split[1])
if typePart == "missionstage" then
local orderNumberString = string.lower(split[2])
---@type StageType
local stageType = "PrimaryStage"
@@ -161,15 +159,16 @@ function GlobalStageManager:ParseStageName(stageName)
local first = orderNumberString:sub(1, 1)
if tonumber(first) == nil then -- first character is the lane identifier only if it's not a number
stageLaneIdentifier = string.lower(first)
orderNumberString = orderNumberString:sub(2)
end
local orderNumber = tonumber(orderNumberString)
if orderNumber == nil then
return { isValid = false, invalidReason = "Stage zone with name " .. stageName .. " does not have a valid order number : " .. orderNumberString }
return { isValid = false, invalidReason = "Stage zone with name " ..
stageName .. " does not have a valid order number : " .. orderNumberString }
end
local stageDisplayName = split[3]
---@type StageNameParseResult
local result = {
@@ -182,18 +181,19 @@ function GlobalStageManager:ParseStageName(stageName)
}
return result
elseif typePart == "waitingstage" then
local stageType = "WaitingStage"
local orderNumberString = split[2]
local orderNumber = tonumber(orderNumberString)
if orderNumber == nil then
return { isValid = false, invalidReason = "Waiting Stage zone with name " .. stageName .. " does not have a valid order number : " .. orderNumberString }
return { isValid = false, invalidReason = "Waiting Stage zone with name " ..
stageName .. " does not have a valid order number : " .. orderNumberString }
end
local waitingSecondsString = split[3]
local waitingSeconds = tonumber(waitingSecondsString)
if waitingSeconds == nil then
return { isValid = false, invalidReason = "Waiting Stage zone with name " .. stageName .. " does not have a valid amount of seconds parameter : " .. waitingSecondsString }
return { isValid = false, invalidReason = "Waiting Stage zone with name " ..
stageName .. " does not have a valid amount of seconds parameter : " .. waitingSecondsString }
end
local stageDisplayName = "Waiting Stage " .. orderNumber
@@ -209,9 +209,9 @@ function GlobalStageManager:ParseStageName(stageName)
waitingStageSeconds = waitingSeconds
}
return result
end
return { isValid = false, invalidReason = "Stage zone with name " .. stageName .. " has an unrecognized type: " .. typePart }
return { isValid = false, invalidReason = "Stage zone with name " ..
stageName .. " has an unrecognized type: " .. typePart }
end
---@param stage Stage
@@ -223,7 +223,8 @@ function GlobalStageManager:OnStageComplete(stage)
local stageLane = self._stageRepository:getStageLane(laneIdentifier)
if not stageLane or not stageLane:IsCurrentStageIndexComplete() then
self.logger:debug("Stage lane " .. (laneIdentifier or "default") .. " is not complete for stage index " .. stageIndex)
self.logger:debug("Stage lane " ..
(laneIdentifier or "default") .. " is not complete for stage index " .. stageIndex)
return
end
@@ -234,7 +235,7 @@ function GlobalStageManager:OnStageComplete(stage)
stageLane:SetActiveStageIndex(nextStageIndex)
if stageLane:IsDefaultStageLane() then
-- COMPLETION IN THE DEFAULT LANE
-- COMPLETION IN THE DEFAULT LANE
-- 1. Check if any lane is at status "BetweenChapters" and the next chapter start is "next stage index"
local allLanes = self._stageRepository:getAllStageLanes()
@@ -242,7 +243,9 @@ function GlobalStageManager:OnStageComplete(stage)
if lane:GetStageLaneIdentifier() ~= StageLane.DefaultLaneKey and lane:GetStageLaneState() == "BetweenChapters" then
local nextChapterStart = lane:GetNextChapterStart()
if nextChapterStart == nextStageIndex then
self.logger:debug("Lane " .. (lane:GetStageLaneIdentifier() or "default") .. " is at chapter start for next stage index " .. nextStageIndex)
self.logger:debug("Lane " ..
(lane:GetStageLaneIdentifier() or "default") ..
" is at chapter start for next stage index " .. nextStageIndex)
Events.PublishStageNumberChanged(nextStageIndex, lane:GetStageLaneIdentifier())
lane:SetActiveStageIndex(nextStageIndex)
end
@@ -256,7 +259,6 @@ function GlobalStageManager:OnStageComplete(stage)
if defaultStageLane and defaultStageLane:GetStageLaneState() == "BetweenChapters" then
local nextChapterStart = defaultStageLane:GetNextChapterStart()
if nextChapterStart == nextStageIndex then
-- 2. Check if all side lanes are at or beyond the next chapter starts
local allLanes = self._stageRepository:getAllStageLanes()
local allSideLanesReady = true
@@ -265,15 +267,19 @@ function GlobalStageManager:OnStageComplete(stage)
-- Check if the lane is at or beyond the next chapter starts when "InChapter" eq "Active"
if lane:GetStageLaneState() == "InChapter" and lane:GetActiveStageIndex() < nextStageIndex then
allSideLanesReady = false
self.logger:debug("Lane " .. (lane:GetStageLaneIdentifier() or "default") .. " is not ready for next stage index " .. nextStageIndex)
self.logger:debug("Lane " ..
(lane:GetStageLaneIdentifier() or "default") ..
" is not ready for next stage index " .. nextStageIndex)
break
-- When in between chapters, check if the next chapter start is less than the next stage index, meaning there is still stages to be completed before the main lane can be activated again
-- When in between chapters, check if the next chapter start is less than the next stage index, meaning there is still stages to be completed before the main lane can be activated again
elseif lane:GetStageLaneState() == "BetweenChapters" then
local nextChapter = lane:GetNextChapterStart()
if nextChapter and nextChapter < nextStageIndex then
allSideLanesReady = false
self.logger:debug("Lane " .. (lane:GetStageLaneIdentifier() or "default") .. " is not ready for next stage index " .. nextStageIndex)
self.logger:debug("Lane " ..
(lane:GetStageLaneIdentifier() or "default") ..
" is not ready for next stage index " .. nextStageIndex)
break
end
end
@@ -281,13 +287,14 @@ function GlobalStageManager:OnStageComplete(stage)
end
if allSideLanesReady then
self.logger:debug("All side lanes are ready for next stage index " .. nextStageIndex .. ", activating default lane")
self.logger:debug("All side lanes are ready for next stage index " ..
nextStageIndex .. ", activating default lane")
Events.PublishStageNumberChanged(nextStageIndex, nil)
defaultStageLane:SetActiveStageIndex(nextStageIndex)
else
self.logger:debug("Not all side lanes are ready for next stage index " .. nextStageIndex .. ", default lane will not be activated")
self.logger:debug("Not all side lanes are ready for next stage index " ..
nextStageIndex .. ", default lane will not be activated")
end
end
end
end
@@ -308,7 +315,6 @@ end
---@public
function GlobalStageManager:OnStageNumberChanged(stageNumber, stageLaneIdentifier)
-- only react on "main" lane changes, ignore other lanes for now
if stageLaneIdentifier ~= nil then return end
@@ -321,13 +327,13 @@ function GlobalStageManager:OnStageNumberChangeComplete(stageNumber, stageLaneId
if stageLaneIdentifier ~= nil then return end
self.logger:debug("Stage number change complete to: " .. tostring(stageNumber))
---@type Array<Group>
local groups = {}
for _, player in pairs(DcsUtil.getAllPlayerUnits()) do
local group = player:getGroup()
if group then
groups[group:getID()] = group
table.insert(groups, group)
end
end
@@ -349,91 +355,98 @@ function GlobalStageManager:UpdateDrawings(stageNumber, stageLaneIdentifier)
if laneIdentifier == stageLaneIdentifier then
if stageNumber >= startStage and stageNumber < stopStage then
self.logger:debug("Drawing " .. drawing:GetName() .. " is active for stage number: " .. tostring(stageNumber))
self.logger:debug("Drawing " ..
drawing:GetName() .. " is active for stage number: " .. tostring(stageNumber))
drawing:Draw()
else
self.logger:debug("Drawing " .. drawing:GetName() .. " is not active for stage number: " .. tostring(stageNumber))
self.logger:debug("Drawing " ..
drawing:GetName() .. " is not active for stage number: " .. tostring(stageNumber))
drawing:Remove()
end
end
end
end
function GlobalStageManager:PrintMermaidStage()
local lanes = self._stageRepository:getAllStageLanes()
---@type table<string, string>
local nodes = {}
---@type table<string, string>
local edges = {}
---@type table<string, Array<number>>
local stageIndicesByLane = {} -- Track all stage indices per lane
local mainLaneId = "default"
-- Color palette for lanes (26 vibrant colors optimized for dark mode)
local laneColors = {
"#FF6B6B", -- 1: Red
"#4ECDC4", -- 2: Teal
"#45B7D1", -- 3: Blue
"#FFA502", -- 4: Orange
"#95E1D3", -- 5: Mint
"#F38181", -- 6: Coral
"#AA96DA", -- 7: Purple
"#FCBAD3", -- 8: Pink
"#A8E6CF", -- 9: Light green
"#FFD3B6", -- 10: Peach
"#FFAAA5", -- 11: Light red
"#FF8B94", -- 12: Rose
"#FFEAA7", -- 13: Butter
"#DFE6E9", -- 14: Gray
"#00B894", -- 15: Emerald
"#0984E3", -- 16: Cobalt
"#6C5CE7", -- 17: Indigo
"#A29BFE", -- 18: Lavender
"#FD79A8", -- 19: Magenta
"#FDCB6E", -- 20: Gold
"#6C757D", -- 21: Slate
"#20C997", -- 22: Seafoam
"#E74C3C", -- 23: Scarlet
"#3498DB", -- 24: Dodger blue
"#9B59B6", -- 25: Amethyst
"#1ABC9C", -- 26: Turquoise
"#FF6B6B", -- 1: Red
"#4ECDC4", -- 2: Teal
"#45B7D1", -- 3: Blue
"#FFA502", -- 4: Orange
"#95E1D3", -- 5: Mint
"#F38181", -- 6: Coral
"#AA96DA", -- 7: Purple
"#FCBAD3", -- 8: Pink
"#A8E6CF", -- 9: Light green
"#FFD3B6", -- 10: Peach
"#FFAAA5", -- 11: Light red
"#FF8B94", -- 12: Rose
"#FFEAA7", -- 13: Butter
"#DFE6E9", -- 14: Gray
"#00B894", -- 15: Emerald
"#0984E3", -- 16: Cobalt
"#6C5CE7", -- 17: Indigo
"#A29BFE", -- 18: Lavender
"#FD79A8", -- 19: Magenta
"#FDCB6E", -- 20: Gold
"#6C757D", -- 21: Slate
"#20C997", -- 22: Seafoam
"#E74C3C", -- 23: Scarlet
"#3498DB", -- 24: Dodger blue
"#9B59B6", -- 25: Amethyst
"#1ABC9C", -- 26: Turquoise
}
---@type table<string, string>
local laneColorMap = {} -- Map lane ID to color
---@type number
local colorIndex = 1
-- Build nodes and collect all stage indices per lane
for _, lane in ipairs(lanes) do
local laneId = lane:GetStageLaneIdentifier() or "default"
local stageIndices = lane:GetAllStageIndices()
stageIndicesByLane[laneId] = stageIndices
-- Assign color to this lane
laneColorMap[laneId] = laneColors[colorIndex]
colorIndex = colorIndex + 1
if colorIndex > #laneColors then
colorIndex = 1
end
-- Create nodes for each stage index in this lane
for _, stageIndex in ipairs(stageIndices) do
local stages = lane:GetStagesAtIndex(stageIndex)
if stages then
for _, stage in ipairs(stages) do
local nodeId = laneId .. "_" .. stageIndex
local stageType = stage:GetStageType()
local stageName = stage.stageName or stage.zoneName
-- Generate bracket label: [1], [2] for default, [w1], [e2] for other lanes
---@type string
local bracketLabel
if laneId == "default" then
bracketLabel = "[" .. stageIndex .. "]"
else
bracketLabel = "[" .. laneId .. stageIndex .. "]"
end
-- Combined label: [bracket] Name
local label = bracketLabel .. " " .. stageName
-- Different node shapes for different stage types
local nodeShape = "["
local nodeEnd = "]"
@@ -444,14 +457,14 @@ function GlobalStageManager:PrintMermaidStage()
nodeShape = "[["
nodeEnd = "]]"
end
nodes[nodeId] = string.format(' %s%s"%s"%s',
nodes[nodeId] = string.format(' %s%s"%s"%s',
nodeId, nodeShape, label, nodeEnd)
end
end
end
end
-- Add edges for sequential stages within the same lane
for laneId, stageIndices in pairs(stageIndicesByLane) do
for i = 1, #stageIndices - 1 do
@@ -459,7 +472,7 @@ function GlobalStageManager:PrintMermaidStage()
local toIdx = stageIndices[i + 1]
local fromNodeId = laneId .. "_" .. fromIdx
local toNodeId = laneId .. "_" .. toIdx
-- Check if this is a chapter boundary (gap)
local isChapter = false
for _, lane in ipairs(lanes) do
@@ -469,12 +482,12 @@ function GlobalStageManager:PrintMermaidStage()
break
end
end
local label = isChapter and "|chapter|" or ""
table.insert(edges, string.format(' %s -->%s %s', fromNodeId, label, toNodeId))
end
end
-- Get main lane reference
local mainStageLane = nil
for _, lane in ipairs(lanes) do
@@ -483,15 +496,15 @@ function GlobalStageManager:PrintMermaidStage()
break
end
end
-- Add dependencies from DEFAULT lane to SIDE lanes at chapter starts
-- Side lanes activate when main completes the stage BEFORE the chapter start
if mainStageLane then
for _, lane in ipairs(lanes) do
if lane:IsDefaultStageLane() == false then
local sideId = lane:GetStageLaneIdentifier()
local sideIndices = stageIndicesByLane[sideId]
local sideIndices = stageIndicesByLane[sideId] or {} --[[@as Array<number>]]
-- For each stage in the side lane
for _, stageIdx in ipairs(sideIndices) do
-- Check if this is a chapter start in the side lane
@@ -506,40 +519,40 @@ function GlobalStageManager:PrintMermaidStage()
end
end
end
-- Add dependencies from SIDE lanes gating the MAIN lane at chapter boundaries
-- Main cannot advance to the next chapter until ALL side lanes are at or above that chapter index
if mainStageLane then
local mainIndices = stageIndicesByLane[mainLaneId]
-- For each chapter start in main lane (skip first one)
for i = 2, #mainIndices do
local nextIdx = mainIndices[i]
-- Check if nextIdx is a chapter start (there's a gap before it)
if mainStageLane:IsChapterStart(nextIdx) then
-- Before main can progress to nextIdx, all side lanes must be at >= nextIdx
for _, lane in ipairs(lanes) do
if lane:IsDefaultStageLane() == false then
local sideId = lane:GetStageLaneIdentifier()
local sideIndices = stageIndicesByLane[sideId]
local sideIndices = stageIndicesByLane[sideId] or {} --[[@as Array<number>]]
-- Find the appropriate gate node for this side lane
-- Use first stage >= nextIdx if it exists, otherwise use the highest stage
local gateStageIdx = nil
for _, idx in ipairs(sideIndices) do
if idx >= nextIdx then
gateStageIdx = idx
break
end
end
-- If no stage >= nextIdx, use the highest stage in this lane
if gateStageIdx == nil and #sideIndices > 0 then
gateStageIdx = sideIndices[#sideIndices]
end
if gateStageIdx then
local fromNodeId = sideId .. "_" .. gateStageIdx
local toNodeId = mainLaneId .. "_" .. nextIdx
@@ -550,25 +563,26 @@ function GlobalStageManager:PrintMermaidStage()
end
end
end
-- Build complete Mermaid diagram
local diagramLines = {"graph TD"}
local diagramLines = { "graph TD" }
-- Add class definitions for each lane with colors and contrasting text
for laneId, color in pairs(laneColorMap) do
table.insert(diagramLines, string.format(' classDef lane_%s fill:%s,stroke:#333,stroke-width:2px,color:#000', laneId, color))
table.insert(diagramLines,
string.format(' classDef lane_%s fill:%s,stroke:#333,stroke-width:2px,color:#000', laneId, color))
end
-- Add nodes
for _, nodeStr in pairs(nodes) do
table.insert(diagramLines, nodeStr)
end
-- Add edges
for _, edgeStr in pairs(edges) do
table.insert(diagramLines, edgeStr)
end
-- Apply classes to nodes
for nodeId in pairs(nodes) do
local laneId = nodeId:match("(.+)_[0-9]+$")
@@ -576,9 +590,10 @@ function GlobalStageManager:PrintMermaidStage()
table.insert(diagramLines, string.format(' class %s lane_%s', nodeId, laneId))
end
end
-- Print as single multi-line message
local diagram = "========== STAGE FLOW DIAGRAM ==========\n" .. table.concat(diagramLines, "\n") .. "\n========== END DIAGRAM =========="
local diagram = "========== STAGE FLOW DIAGRAM ==========\n" ..
table.concat(diagramLines, "\n") .. "\n========== END DIAGRAM =========="
self.logger:info(diagram)
end
@@ -586,10 +601,10 @@ end
---@param stageNumber number
---@param stageLaneIdentifier string? nil for default lan
---@return boolean | nil
GlobalStageManager.isStageComplete = function (stageNumber, stageLaneIdentifier)
GlobalStageManager.isStageComplete = function(stageNumber, stageLaneIdentifier)
if singletonInstance == nil then
Logger.new("StageManager", "INFO"):warn("GlobalStageManager.isStageComplete called before GlobalStageManager was initialized. Returning nil")
Logger.new("StageManager", "INFO"):warn(
"GlobalStageManager.isStageComplete called before GlobalStageManager was initialized. Returning nil")
return nil
end
@@ -41,7 +41,7 @@ end
function SpearheadGroup:SpawnCorpsesOnly()
if self._isSpawned == true then return end
self._spawnManager:SpawnCorpsesOnly(self._groupName)
self._isSpawned = true
@@ -57,7 +57,7 @@ function SpearheadGroup:Spawn(lateStart)
uncontrolled = lateStart,
}
local spawnedObject, isStatic = self._spawnManager:SpawnGroup(self._groupName, overrides, self._isPersistent)
local _, isStatic = self._spawnManager:SpawnGroup(self._groupName, overrides, self._isPersistent)
self._isStatic = isStatic
self._isSpawned = true
end
@@ -91,13 +91,13 @@ function SpearheadGroup:GetCoalition()
end
---comment
---@return table result list of objects
---@return Array<Unit|StaticObject> result list of objects
function SpearheadGroup:GetObjects()
---@type Array<Unit|StaticObject>
local result = {}
if self._isStatic == true then
local staticObject = StaticObject.getByName(self._groupName)
if staticObject then
if staticObject then
table.insert(result, staticObject)
end
else
@@ -105,7 +105,7 @@ function SpearheadGroup:GetObjects()
if not group then return {} end
for _, unit in pairs(group:getUnits()) do
table.insert(result, unit)
end
end
end
return result
end
@@ -123,7 +123,7 @@ function SpearheadGroup:GetAsUnits()
if not group then return {} end
for _, unit in pairs(group:getUnits()) do
table.insert(result, unit)
end
end
return result
end
@@ -133,7 +133,7 @@ function SpearheadGroup:GetAllUnitPositions()
local result = {}
if self._isStatic == true then
local staticObject = StaticObject.getByName(self._groupName)
if staticObject then
if staticObject then
table.insert(result, staticObject:getPoint())
end
else
@@ -141,7 +141,7 @@ function SpearheadGroup:GetAllUnitPositions()
if not group then return {} end
for _, unit in pairs(group:getUnits()) do
table.insert(result, unit:getPoint())
end
end
end
return result
end
@@ -50,16 +50,10 @@ function BlueSam.New(database, logger, zoneName, spawnManager)
---@type table<string, Vec3>
local redUnitsPos = {}
local buildable = false
if self._buildableCrateKilos and self._buildableCrateKilos > 0 then
buildable = true
end
for _, groupName in pairs(blueSamData.groups) do
local spearheadGroup = SpearheadGroup.New(groupName, spawnManager, true)
if spearheadGroup then
if spearheadGroup:GetCoalition() == 2 or spearheadGroup:GetCoalition() == 0 then
table.insert(self._blueGroups, spearheadGroup)
end
@@ -78,7 +72,7 @@ function BlueSam.New(database, logger, zoneName, spawnManager)
--Cleanup units
local cleanup_distance = 5
for blueUnitName, blueUnitPos in pairs(blueUnitsPos) do
for _, blueUnitPos in pairs(blueUnitsPos) do
for redUnitName, redUnitPos in pairs(redUnitsPos) do
local distance = Util.VectorDistance3d(blueUnitPos, redUnitPos)
if distance <= cleanup_distance then
@@ -140,7 +134,9 @@ end
function BlueSam:SpawnGroups()
for unitName, needsCleanup in pairs(self._cleanupUnits) do
DcsUtil.DestroyUnit(unitName)
if needsCleanup then
DcsUtil.DestroyUnit(unitName)
end
end
for _, group in pairs(self._blueGroups) do
@@ -42,11 +42,11 @@ function FarpZone.New(database, logger, zoneName, spawnManager)
self._groups = {}
self._padNames = {}
self._supplyHubs = {}
if farpData then
self._padNames = farpData.padNames
for _, supplyHubName in pairs(farpData.supplyHubNames) do
local supplyHub = SupplyHub.new(database, logger, supplyHubName)
if supplyHub then
@@ -55,7 +55,7 @@ function FarpZone.New(database, logger, zoneName, spawnManager)
end
for _, groupName in pairs(farpData.groups) do
for _, groupName in pairs(farpData.groups) do
local group = SpearheadGroup.New(groupName, spawnManager, true)
table.insert(self._groups, group)
group:Destroy()
@@ -90,7 +90,7 @@ function StageBase.New(databaseManager, logger, airbaseName, spawnManager)
local cleanup_distance = 5
for blueUnitName, blueUnitPos in pairs(blueUnitsPos) do
for _, blueUnitPos in pairs(blueUnitsPos) do
for redUnitName, redUnitPos in pairs(redUnitsPos) do
local distance = Util.VectorDistance3d(blueUnitPos, redUnitPos)
if distance <= cleanup_distance then
@@ -113,6 +113,7 @@ end
function StageBase:SpawnRedUnits()
---comment
---@param groups Array<SpearheadGroup>
---@return number|nil
local spawnAsync = function(groups)
for _, group in pairs(groups) do
group:Spawn()
@@ -142,6 +143,7 @@ end
function StageBase:SpawnBlueUnits()
---comment
---@param groups Array<SpearheadGroup>
---@return number|nil
local spawnAsync = function(groups)
for _, group in pairs(groups) do
group:Spawn()
@@ -172,7 +174,7 @@ function StageBase:ActivateBlueStage()
else
self:FinaliseBlueStage()
end
end
function StageBase:FinaliseBlueStage()
@@ -42,7 +42,7 @@ function SupplyHub.new(database, logger, zoneName)
end
self._zone = DcsUtil.getZoneByName(zoneName)
self._supplyUnitsTracker = SupplyUnitsTracker.getOrCreate()
self._inZone = {}
self._missionCommandsHelper = MissionCommandsHelper.getOrCreate()
@@ -37,6 +37,7 @@ function BuildableZone:New(targetZone, kilosRequired, crateType, buildableGroup
---@param params UnpackCrateParam
---@param time number
---@return number|nil
local startUnpackingCrate = function(params, time)
local unpacked = params.unpackedKilos + (params.kilosPerSecond * 2)
local alreadySpawned = params.unpackedItems / params.groupsPerKilo
@@ -50,7 +51,7 @@ function BuildableZone:New(targetZone, kilosRequired, crateType, buildableGroup
if params.unpackedKilos >= params.kilos or spawned == false then
return
end
return time + 0.5
end
@@ -76,8 +77,6 @@ function BuildableZone:New(targetZone, kilosRequired, crateType, buildableGroup
self._buildableMission = nil
end
if self._buildableMission == nil then
self._buildableLogger:debug("No buildable mission for zone: " .. targetZone.name)
end
@@ -99,25 +98,14 @@ function BuildableZone:OnBuildingComplete() end
---@field unpackedItems number
---@field unpackedKilos number
---@param mission BuildableMission?
---@param _ BuildableMission?
---@param kilos number
function BuildableZone:OnCrateDroppedOff(mission, kilos)
function BuildableZone:OnCrateDroppedOff(_, kilos)
self._buildableLogger:debug("Crate dropped off in zone: " .. self._targetZone.name)
local timeToUnpack = (kilos / 500) * 15
---@type UnpackCrateParam
local params = {
self = self,
groupsPerKilo = self._groupsPerKilo,
unpackedItems = 0,
kilosPerSecond = kilos/timeToUnpack,
unpackedKilos = 0,
kilos = kilos
}
---@param params UnpackCrateParam
---@param time number
---@return number|nil
local startUnpackingCrate = function(params, time)
local unpacked = params.unpackedKilos + (params.kilosPerSecond * 2)
local alreadySpawned = params.unpackedItems / params.groupsPerKilo
@@ -132,10 +120,21 @@ function BuildableZone:OnCrateDroppedOff(mission, kilos)
params.self:FinaliseCrate(params.kilos)
return
end
return time + 2
end
local timeToUnpack = (kilos / 500) * 15
---@type UnpackCrateParam
local params = {
self = self,
groupsPerKilo = self._groupsPerKilo,
unpackedItems = 0,
kilosPerSecond = kilos/timeToUnpack,
unpackedKilos = 0,
kilos = kilos
}
timer.scheduleFunction(startUnpackingCrate, params, timer.getTime() + 2)
end
@@ -190,7 +189,7 @@ function BuildableZone:SpawnAmount(amount)
return nil
end
for i = 1, amount do
for _ = 1, amount do
local spawned = spawnOne()
if spawned ~= true then
self._buildableLogger:debug("No more groups to spawn in zone: " .. self._targetZone.name)
+12 -9
View File
@@ -4,8 +4,8 @@
---@field private _isDefaultStageLane boolean
---@field private _stageLaneIdentifier string
---@field private _activeStageIndex number
---@field private _stagesInLaneByIndex table<number, Array<Stage>>
---@field private _chapterStarts table<number, boolean>?
---@field private _stagesInLaneByIndex table<string, Array<Stage>>
---@field private _chapterStarts table<string, boolean>?
---@field private _stageLaneState StageLaneState
---@field private _maxStageIndex number
local StageLane = {}
@@ -16,6 +16,7 @@ StageLane.__index = StageLane
---| "BetweenChapters"
---| "Completed"
---@type string?
StageLane.DefaultLaneKey = nil
function StageLane.New(laneIdentifier)
@@ -37,21 +38,21 @@ end
---@param stage Stage
function StageLane:AddStage(stage)
local stageIndex = stage:GetStageIndex()
if not self._stagesInLaneByIndex[stageIndex] then
self._stagesInLaneByIndex[stageIndex] = {}
if not self._stagesInLaneByIndex[tostring(stageIndex)] then
self._stagesInLaneByIndex[tostring(stageIndex)] = {}
end
if not self._maxStageIndex or stageIndex > self._maxStageIndex then
self._maxStageIndex = stageIndex
end
table.insert(self._stagesInLaneByIndex[stageIndex], stage)
table.insert(self._stagesInLaneByIndex[tostring(stageIndex)], stage)
end
---@param stageNumber number
---@return boolean?
function StageLane:IsStageIndexComplete(stageNumber)
local stages = self._stagesInLaneByIndex[stageNumber]
local stages = self._stagesInLaneByIndex[tostring(stageNumber)]
if not stages then
return nil
end
@@ -88,7 +89,7 @@ end
---@param stageNumber number
function StageLane:SetActiveStageIndex(stageNumber)
if self._stagesInLaneByIndex[stageNumber] == nil then
if self._stagesInLaneByIndex[tostring(stageNumber)] == nil then
self._stageLaneState = "BetweenChapters" -- stage number is not in this lane, so we are between chapters
elseif stageNumber > self._maxStageIndex then
self._stageLaneState = "Completed" -- stage number is beyond the max stage index
@@ -108,6 +109,7 @@ end
--- Chapter starts are the first StageIndex after a gap in stage numbers. <br>
--- For example, if the stage numbers are 1, 2, 3, 5, 6, 7, then stage number 5 is a chapter start because there is a gap between 3 and 5. <br>
---@param stageNumber number
---@return boolean
function StageLane:IsChapterStart(stageNumber)
if self._chapterStarts == nil then
@@ -123,13 +125,14 @@ end
function StageLane:FillChapterStarts()
local previousIndex = nil
---@type table<string, number>
local stageIndices = {}
for stageIndex, _ in pairs(self._stagesInLaneByIndex) do
table.insert(stageIndices, tonumber(stageIndex))
end
table.sort(stageIndices)
self._chapterStarts = self._chapterStarts or {}
if self._chapterStarts == nil then self._chapterStarts = {} end
for _, stageIndex in ipairs(stageIndices) do
if previousIndex == nil then
-- First stage is always a chapter start
@@ -158,7 +161,7 @@ end
---@param stageIndex number
---@return Array<Stage>?
function StageLane:GetStagesAtIndex(stageIndex)
return self._stagesInLaneByIndex[stageIndex]
return self._stagesInLaneByIndex[tostring(stageIndex)]
end
---@return number?
@@ -49,7 +49,6 @@ end
function StageRepository:AddStage(stage)
local stageLaneIdentifier = stage:GetStageLaneIdentifier() or StageLane.DefaultLaneKey
if self.StageLanes[tostring(stageLaneIdentifier)] == nil then
self.StageLanes[tostring(stageLaneIdentifier)] = StageLane.New(stageLaneIdentifier)
end
@@ -105,7 +105,7 @@ function Stage:superNew(database, stageConfig, logger, initData, stageType, miss
end
self.stageName = initData.stageDisplayName
self._stageType = stageType
self.OnPostStageComplete = nil
self.OnPostBlueActivated = nil
@@ -160,25 +160,26 @@ function Stage:superNew(database, stageConfig, logger, initData, stageType, miss
self._logger:info("Initiating new Stage with name: " .. self.zoneName)
---comment
---@param self Stage
---@param selfA Stage
---@param time number?
self.CheckContinuousAsync = function (self, time)
self:CheckAndUpdateSelf()
if self:IsComplete() == true then
self:NotifyComplete()
---@return number|nil
self.CheckContinuousAsync = function (selfA, time)
selfA:CheckAndUpdateSelf()
if selfA:IsComplete() == true then
selfA:NotifyComplete()
return nil
end
return time + 20
end
do -- load tables
local missionZones = database:getMissionsForStage(self.zoneName)
self._logger:debug("Found " .. Util.tableLength(missionZones) .. " mission zones for stage: " .. self.zoneName)
for _, missionZone in pairs(missionZones) do
local mission = ZoneMission.new(missionZone, self._missionPriority, database, logger, self, spawnManager)
if mission then
self._db.missionsByCode[mission.code] = mission
@@ -233,7 +234,7 @@ function Stage:superNew(database, stageConfig, logger, initData, stageType, miss
table.insert(self._db.missions, mission)
end
end
else
else
self._logger:info("Using persisted random mission with name: " .. missionName .. " and zone: " .. missionZonePicked)
for _, mission in pairs(missions) do
if string.lower(mission.zoneName) == string.lower(missionZonePicked) then
@@ -284,14 +285,14 @@ end
function Stage:IsComplete()
if self._currentStageState >= StageState.Blue then return true end
for i, mission in pairs(self._db.sams) do
for _, mission in pairs(self._db.sams) do
local state = mission:getState()
if state == "ACTIVE" or state == "NEW" or state =="WAITING" then
return false
end
end
for i, mission in pairs(self._db.missions) do
for _, mission in pairs(self._db.missions) do
local state = mission:getState()
if state == "ACTIVE" or state == "NEW" then
return false
@@ -325,7 +326,9 @@ function Stage:GetStageName()
return self.stageName
end
---@return Array<Mission>
function Stage:GetMissions()
---@type Array<Mission>
local missions = {}
for _, mission in pairs(self._db.missions) do
table.insert(missions, mission)
@@ -370,9 +373,9 @@ function Stage:CheckAndUpdateSelf()
local availableMissionsCount = Util.tableLength(getAvailableMissions())
local activeCount = getActiveMissionsCount()
if activeCount < max and availableMissionsCount > 0 then
for i = activeCount+1, max do
for _ = activeCount+1, max do
if availableMissionsCount == 0 then
i = max+1 --exits this loop
break
else
local mission = Util.randomFromList(getAvailableMissions()) --[[@as Mission]]
if mission then
@@ -426,7 +429,7 @@ function Stage:PreActivate()
end
self._currentStageState = StageState.PreActivated
for key, mission in pairs(self._db.sams) do
for _, mission in pairs(self._db.sams) do
if mission then
mission:SpawnInactive()
end
@@ -473,7 +476,7 @@ function Stage:MarkStage()
drawing.colorString = DrawingHelper.ColorTableToColorString(Stage.StageColors.INVISIBLE)
drawing.style = "no line"
end
return drawing
end)
self._customDrawing:Draw()
@@ -585,12 +588,12 @@ function Stage:OnStageNumberChanged(number, stageLaneIdentifier)
if needsBlueActivation() == true then
self:ActivateBlueStage()
end
end
---@param self Stage
---@param mission Mission
Stage.OnMissionComplete = function(self, mission)
---@param _ Mission
Stage.OnMissionComplete = function(self, _)
self:CheckAndUpdateSelf()
end
@@ -645,7 +648,7 @@ function Stage:GetStageStats()
end
end
for _, mission in pairs(self._db.sams) do
for _, _ in pairs(self._db.sams) do
dead = dead + 1
end
@@ -673,13 +676,14 @@ function Stage:ActivateBlueStage()
miscGroup:Spawn()
end
---@param self Stage
local ActivateBlueAsync = function(self)
---@param selfA Stage
---@return number|nil
local ActivateBlueAsync = function(selfA)
pcall(function()
self:MarkStage()
selfA:MarkStage()
end)
self:ActivateBlueGroups()
selfA:ActivateBlueGroups()
return nil
end
@@ -23,10 +23,9 @@ function ExtraStage.New(database, stageConfig, logger, initData, spawnManager)
self:superNew(database, stageConfig, logger, initData, "ExtraStage", "secondary", spawnManager)
self.OnPostBlueActivated = function (selfStage)
selfStage:MarkStage()
end
self.OnPostStageComplete = function (selfStage)
selfStage:ActivateBlueStage()
end
@@ -48,7 +47,6 @@ function ExtraStage:OnStageNumberChanged(number, stageLaneIdentifier)
return
end
local previousActive = self._activeStage
self._activeStage = number
if self.stageNumber - self._activeStage == self._stageConfig.AmountPreactivateStage then
@@ -62,7 +60,7 @@ function ExtraStage:OnStageNumberChanged(number, stageLaneIdentifier)
self:ActivateStage()
end
if self._currentStageState == StageState.BLUE then
if self._currentStageState == StageState.Blue then
self:ActivateBlueStage()
end
@@ -25,7 +25,7 @@ function WaitingStage.New(database, stageConfig, logger, initData, waitingSecond
self._startTime = nil
self.CheckContinuousAsync = function (selfA, time)
if selfA:IsComplete() == true then
selfA:NotifyComplete()
return nil
@@ -47,7 +47,7 @@ function WaitingStage:ActivateStage()
timer.scheduleFunction(self.CheckContinuousAsync, self, self._startTime + self._waitTimeSeconds)
end
function WaitingStage:IsComplete()
function WaitingStage:IsComplete()
if timer.getTime() > (self._startTime + self._waitTimeSeconds) then return true end
return false
end
@@ -56,12 +56,12 @@ function WaitingStage:OnStageNumberChanged()
self._logger:debug("Waiting Stage OnStageNumberChanged override")
end
function WaitingStage:MarkStage(stageColor)
function WaitingStage:MarkStage(_)
self._logger:debug("Waiting Stage MarkStage override")
end
function WaitingStage:GetExpectedTime()
return self._startTime + self._waitTimeSeconds
return self._startTime + self._waitTimeSeconds
end
return WaitingStage
@@ -1,7 +1,5 @@
local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelper")
local Util = require("classes.util.Util")
local Logger = require("classes.util.Logger")
local MissionEditorWarnings = require("classes.util.MissionEditorWarnings")
local drawingLogger = Logger.new("CustomDrawing")
@@ -1,4 +1,3 @@
local Util = require("classes.util.Util")
local DcsUtil = require("classes.util.DcsUtil")
local Logger = require("classes.util.Logger")
local GlobalConfig = require("classes.configuration.GlobalConfig")
@@ -55,7 +54,7 @@ local function MarkupToAll(shapeID, drawID, points, fillColor, lineColor, lineS
if lineThickness == nil or lineThickness <= 0 then
lineStyle = 0
end
---@type string
local functionString = "trigger.action.markupToAll(" .. shapeID .. ", -1, " .. drawID .. ","
for _, point in pairs(points) do
@@ -78,7 +77,7 @@ local function MarkupToAll(shapeID, drawID, points, fillColor, lineColor, lineS
end
trigger.action.setMarkupColor(drawID, lineColor)
trigger.action.setMarkupTypeLine(drawID, lineStyle)
end
---@private
@@ -126,14 +125,14 @@ function DrawingHelper.DrawPolygon(object)
local fillColor = DrawingHelper.ColorToColorTable(free.fillColorString)
local color = DrawingHelper.ColorToColorTable(free.colorString)
local lineStyle = DrawingHelper.ToLineStyleInteger(free.style)
---@type Array<number>
local keys = {}
for k, _ in pairs(free.points) do
table.insert(keys, k)
end
table.sort(keys, function(a, b) return a < b end)
---@type Array<Vec3>
local points = {}
for _, k in ipairs(keys) do
local point = free.points[k]
@@ -201,7 +200,7 @@ function DrawingHelper.DrawLine(object)
---@type Array<Vec3>
local points = {}
local ids = {}
local ids = {}
for _, point in ipairs(object.points) do
table.insert(points, { x = object.mapX + point.x, y = 0, z = object.mapY + point.y } )
@@ -265,7 +264,7 @@ function DrawingHelper.ColorTableToColorString(rgba)
logger:warn("ColorTableToColorString called with invalid rgba table, returning default color string '0x00000000'")
return "0x00000000"
end
local r = string.format("%02X", math.floor(rgba[1] * 255))
local g = string.format("%02X", math.floor(rgba[2] * 255))
local b = string.format("%02X", math.floor(rgba[3] * 255))
@@ -275,6 +274,7 @@ function DrawingHelper.ColorTableToColorString(rgba)
end
---@param lineStyle string
---@return integer
function DrawingHelper.ToLineStyleInteger(lineStyle)
lineStyle = lineStyle:lower()
if lineStyle == "no line" then
@@ -1,6 +1,5 @@
local Logger = require("classes.util.Logger")
local Util = require("classes.util.Util")
local DcsUtil = require("classes.util.DcsUtil")
---@class BattleManager
---@field private _name string
@@ -13,8 +12,6 @@ local DcsUtil = require("classes.util.DcsUtil")
local BattleManager = {}
BattleManager.__index = BattleManager
local debugDrawing = false
---@param redGroups Array<SpearheadGroup>
---@param blueGroups Array<SpearheadGroup>
---@param name string
@@ -30,8 +27,8 @@ function BattleManager.New(redGroups, blueGroups, name, logLevel)
self._redGroups = redGroups
self._blueGroups = blueGroups
self._logger:debug("BattleManager created with name: " .. self._name
.. ", red groups: " .. #self._redGroups
self._logger:debug("BattleManager created with name: " .. self._name
.. ", red groups: " .. #self._redGroups
.. ", blue groups: " .. #self._blueGroups)
return self
@@ -39,6 +36,7 @@ end
---@param self BattleManager
---@param time number
---@return number?
local function CheckTask(self, time)
local interval = self:Update()
if not interval then return end
@@ -90,8 +88,6 @@ function BattleManager:Update()
self._logger:debug("BattleManager Update called for " .. self._name)
local shootChance = 1 -- Adjust this value to control the shooting probability (0.0 to 1.0)
self:LetUnitsShoot(self._redGroups, self._blueGroups)
self:LetUnitsShoot(self._blueGroups, self._redGroups)
@@ -136,7 +132,7 @@ function BattleManager:LetUnitsShoot(groups, targetGroups)
expendQtyEnabled = true
}
}
local controller = unit:getController()
if controller then
controller:setTask(shootTask)
@@ -146,14 +142,14 @@ function BattleManager:LetUnitsShoot(groups, targetGroups)
end
end
end
end
end
---@param unit Unit
---@return number
---@return number
function BattleManager:getBestAmmo(unit)
local ammo = unit:getAmmo()
local ammo = unit:getAmmo() --[[@as table<number, table>]]
if not ammo then return 3221225470, 1 end -- Default ammo if no ammo is found
@@ -167,9 +163,9 @@ function BattleManager:getBestAmmo(unit)
end
end
local entry = Util.randomFromList(shells)
local entry = Util.randomFromList(shells) --[[@as table]]
if entry and entry.desc and entry.desc.warhead then
local caliber = entry.desc.warhead.caliber
local caliber = entry.desc.warhead.caliber --[[@as number]]
if caliber > 50 then
return 258503344128, 1
else
@@ -196,7 +192,7 @@ function BattleManager:IsUnitApplicable(unit)
end
return true
end
---@private
@@ -214,6 +210,7 @@ function BattleManager:ToShootingHulls(groups)
end
local hulls = Util.getSeparatedConvexHulls(points, 50)
---@type Array<Array<Vec2>>
local enlargedHulls = {}
for _, hull in pairs(hulls) do
local enlarged = Util.enlargeConvexHull(hull, 25)
@@ -236,7 +233,7 @@ end
---@param groupHulls Array<Array<Vec2>>
---@return Vec2?
function BattleManager:GetRandomPoint(origin, groupHulls)
local hull = Util.randomFromList(groupHulls) --[[@as Array<Vec2>]]
if not hull then return nil end
local shootPoints = Util.GetTangentHullPointsFromOrigin(hull, origin)
@@ -7,7 +7,7 @@ local SupplyConfigHelper = require("classes.stageClasses.helpers.SupplyConfigHel
local StageConfig = require("classes.configuration.StageConfig")
---@class MissionCommandsHelper
---@class MissionCommandsHelper : OnPlayerEnterUnitListener
---@field missionsByCode table<string, Mission> @table of missions by their code
---@field enabledByCode table<string, boolean> @table of enabled missions by their code
---@field updateNeeded boolean @flag to indicate if an update is needed
@@ -31,8 +31,6 @@ local function sortMissions(list, groupPos)
end)
end
local id = 0
local instance = nil
---@return MissionCommandsHelper
@@ -57,16 +55,16 @@ function MissionCommandsHelper.getOrCreate()
instance._supplyUnitsTracker:AddOnSupplyUnitEventListener(
{
enteredSupplyHub = function(self, unit)
enteredSupplyHub = function(_, unit)
if unit == nil then return end
instance.updateNeeded = true
instance:updateCommandsForGroup(unit:getGroup():getID())
end,
exitedSupplyHub = function(self, unit)
exitedSupplyHub = function(_, unit)
instance.updateNeeded = true
instance:updateCommandsForGroup(unit:getGroup():getID())
end,
supplyUnitSpawned = function(self, unit)
supplyUnitSpawned = function(_, unit)
instance.updateNeeded = true
instance:updateCommandsForGroup(unit:getGroup():getID())
end
@@ -371,7 +369,7 @@ function MissionCommandsHelper:AddAllMissionCommandsToGroup(groupID)
do --- secondary missions
local count = 0
local path = { [1] = folderNames.secondary }
---@type Array<Mission>
local secondaryMissions = {}
for code, enabled in pairs(self.enabledByCode) do
if enabled == true then
@@ -509,7 +507,6 @@ function MissionCommandsHelper:AddCargoCommands(groupID)
local unloadCargoCommand = function(params)
local unitID = params.unitID
local crateType = params.crateType
local supplyUnitsTracker = params.supplyUnitsTracker
params.supplyUnitsTracker:UnloadRequested(unitID, crateType, params.commandHelper)
end
@@ -518,7 +515,7 @@ function MissionCommandsHelper:AddCargoCommands(groupID)
for cargoType, amount in pairs(cargo) do
local cargoConfig = SupplyConfigHelper.getSupplyConfig(cargoType)
if cargoConfig then
for i = 1, amount do
for _ = 1, amount do
local path = { [1] = folderNames.cargo }
---@type UnloadCargoCommandParams
local params = { unitID = unit:getID(), crateType = cargoType, supplyUnitsTracker = self
@@ -83,7 +83,8 @@ function SupplyConfigHelper.fromObjectName(name)
return nil
end
---@param type CrateType
---@param type CrateType
---@return SupplyConfig?
function SupplyConfigHelper.getSupplyConfig(type)
return SupplyConfig[type]
end
@@ -29,7 +29,7 @@ local SupplyLoadConfig = {
{ centerAngle = 270, angleWidth = 60, minRadius = 15, maxRadius = 50, spacing = 5 },
{ centerAngle = 90, angleWidth = 60, minRadius = 15, maxRadius = 50, spacing = 5 },
}
},
},
["UH-1H"] = {
maxInternalLoad = 2000,
dropZones = {
@@ -10,7 +10,7 @@ local SupplyLoadConfig = require("classes.stageClasses.helpers.SupplyLoadConfig"
---@field enteredSupplyHub fun(self:SupplyUnitEventListener, unit:Unit, hub:SupplyHub) | nil
---@field exitedSupplyHub fun(self:SupplyUnitEventListener, unit:Unit, hub:SupplyHub) | nil
---@class SupplyUnitsTracker
---@class SupplyUnitsTracker : OnPlayerEnterUnitListener
---@field private _supplyUnitsByName table<string, Unit>
---@field private _cargoInUnits table<string, table<CrateType, number>>
---@field private _logger Logger
@@ -43,6 +43,8 @@ function SupplyUnitsTracker.getOrCreate()
SpearheadEvents.AddOnPlayerEnterUnitListener(singleton)
---@param selfA SupplyUnitsTracker
---@param time number
---@return number?
local function updateTask(selfA, time)
selfA:Update()
@@ -134,6 +136,7 @@ end
---@private
---@param unit Unit
---@return boolean
function SupplyUnitsTracker:IsSupplyUnit(unit)
if unit == nil then return false end
@@ -158,15 +161,17 @@ function SupplyUnitsTracker:AddCargoToUnit(unitID, crateType)
local unit = DcsUtil.GetPlayerUnitByID(unitID)
if unit == nil then return end
if self._cargoInUnits[unitID] == nil then
self._cargoInUnits[unitID] = {}
local unitIdStr = tostring(unitID)
if self._cargoInUnits[unitIdStr] == nil then
self._cargoInUnits[unitIdStr] = {}
end
if self._cargoInUnits[unitID][crateType] == nil then
self._cargoInUnits[unitID][crateType] = 0
if self._cargoInUnits[unitIdStr][crateType] == nil then
self._cargoInUnits[unitIdStr][crateType] = 0
end
self._cargoInUnits[unitID][crateType] = self._cargoInUnits[unitID][crateType] + 1
self._cargoInUnits[unitIdStr][crateType] = self._cargoInUnits[unitIdStr][crateType] + 1
end
@@ -185,7 +190,7 @@ function SupplyUnitsTracker:RemoveCargoFromUnit(unitID, crateType)
self._cargoInUnits[unitIDStr][crateType] = self._cargoInUnits[unitIDStr][crateType] - 1
local hasCargo = false
for type, count in pairs(self._cargoInUnits[unitIDStr]) do
for _, count in pairs(self._cargoInUnits[unitIDStr]) do
if count > 0 then
hasCargo = true
break
@@ -216,13 +221,11 @@ end
function SupplyUnitsTracker:CheckUnitsInZones()
for name, unit in pairs(self._supplyUnitsByName) do
for _, unit in pairs(self._supplyUnitsByName) do
if unit ~= nil and unit:isExist() == true then
self._logger:debug("Checking unit: " .. unit:getName())
local pos = unit:getPoint()
local group = unit:getGroup()
for hub, enabled in pairs(self._registeredHubs) do
if enabled == true then
local zone = hub:GetZone()
@@ -238,7 +241,7 @@ function SupplyUnitsTracker:CheckUnitsInZones()
end)
end
end
else
if self._unitInSupplyHub[tostring(unit:getID())] == true then
self._unitInSupplyHub[tostring(unit:getID())] = false
@@ -255,7 +258,7 @@ function SupplyUnitsTracker:CheckUnitsInZones()
end
end
self._unitPositions[unit:getID()] = pos
self._unitPositions[tostring(unit:getID())] = pos
end
end
end
@@ -292,11 +295,11 @@ local cargoCount = 0
---@param crateType CrateType
---@param missionCommandsHelper MissionCommandsHelper
function SupplyUnitsTracker:UnloadRequested(unitID, crateType, missionCommandsHelper)
self._logger:debug("Unload requested for unit: " .. unitID .. " crateType: " .. crateType)
local unit = DcsUtil.GetPlayerUnitByID(unitID)
if unit == nil or unit:isExist() == false then
if unit == nil or unit:isExist() == false then
self._logger:warn("Unload requested for non-existent unit: " .. unitID)
return
end
@@ -305,7 +308,7 @@ function SupplyUnitsTracker:UnloadRequested(unitID, crateType, missionCommandsHe
self._logger:warn("Unload requested for unit with no group: " .. unit:getName())
return
end
local cargoConfig = SupplyConfigHelper.getSupplyConfig(crateType)
if cargoConfig == nil then
@@ -325,6 +328,7 @@ function SupplyUnitsTracker:UnloadRequested(unitID, crateType, missionCommandsHe
self:UpdateWeightForUnit(unit)
cargoCount = cargoCount + 1
---@type {name: string, type: string, x: number, y: number}
local cargoSpawnObject = {
name = crateType .. "_" .. cargoCount,
type = cargoConfig.staticType,
@@ -362,11 +366,11 @@ function SupplyUnitsTracker:UnitRequestCrateLoading(groupID, crateType, missionC
end
local unit = group:getUnit(1)
if unit == nil then return end
if unit:isExist() == false then return end
if unit:inAir() == true then
trigger.action.outTextForUnit(unit:getID(), "Land first before crates can be loaded", 10)
return
@@ -383,7 +387,7 @@ function SupplyUnitsTracker:UnitRequestCrateLoading(groupID, crateType, missionC
---@param params LoadCargoParams
local LoadCrateTask = function(params)
local loaded = params.self:TryLoadCrateInUnit(params.unit, params.crateType, params.commandHelper)
if loaded ~= false then
trigger.action.outTextForUnit(unit:getID(), "Loaded crate :" .. params.crateType, 10)
@@ -410,7 +414,7 @@ end
---@param commandHelper MissionCommandsHelper
---@return boolean
function SupplyUnitsTracker:TryLoadCrateInUnit(unit, crateType, commandHelper)
local crateConfigA = SupplyConfigHelper.getSupplyConfig(crateType)
if crateConfigA == nil then
trigger.action.outTextForUnit(unit:getID(), "Invalid crate type: " .. crateType, 5)
@@ -444,7 +448,7 @@ function SupplyUnitsTracker:TryLoadCrateInUnit(unit, crateType, commandHelper)
if group == nil then return false end
local groupID = group:getID()
commandHelper:updateCommandsForGroup(groupID)
return true
end
@@ -462,7 +466,7 @@ function SupplyUnitsTracker:UnitRequestCrateSpawn(groupID, crateType)
return
end
end
end
@@ -484,11 +488,11 @@ function SupplyUnitsTracker:GetBoundingBoxes(foundObject)
if desc == nil or desc.box == nil then
return nil
end
local objPos = foundObject:getPoint()
local box = desc.box
local box = desc.box --[[@type table]]
local heading = 0
-- Try to get object heading from position vector's forward direction
-- This works for units and other objects that support getPosition
pcall(function()
@@ -497,13 +501,13 @@ function SupplyUnitsTracker:GetBoundingBoxes(foundObject)
heading = math.atan2(objPosition.x.z, objPosition.x.x)
end
end)
-- For rotated objects, we need to rotate the bounding box
local minX = box.min.x
local maxX = box.max.x
local minZ = box.min.z
local maxZ = box.max.z
local minX = box.min.x --[[@as number]]
local maxX = box.max.x --[[@as number]]
local minZ = box.min.z --[[@as number]]
local maxZ = box.max.z --[[@as number]]
-- If object has significant rotation, apply rotation to bbox corners
if math.abs(heading) > 0.1 then
-- Get all 4 corners of bbox in local space
@@ -513,21 +517,21 @@ function SupplyUnitsTracker:GetBoundingBoxes(foundObject)
{maxX, minZ},
{maxX, maxZ}
}
-- Rotate corners and find new min/max
minX, maxX = math.huge, -math.huge
minZ, maxZ = math.huge, -math.huge
for _, corner in ipairs(corners) do
local rotX = corner[1] * math.cos(heading) - corner[2] * math.sin(heading)
local rotZ = corner[1] * math.sin(heading) + corner[2] * math.cos(heading)
local rotX = corner[1] * math.cos(heading) - corner[2] * math.sin(heading) --[[@as number]]
local rotZ = corner[1] * math.sin(heading) + corner[2] * math.cos(heading) --[[@as number]]
minX = math.min(minX, rotX)
maxX = math.max(maxX, rotX)
minZ = math.min(minZ, rotZ)
maxZ = math.max(maxZ, rotZ)
end
end
-- Convert relative bbox to world space by adding object position
---@type SupplyUnitsBoundingBox
return {
@@ -551,7 +555,7 @@ end
---@param safetyMargin number Safety margin around objects
---@return boolean True if collision detected
function SupplyUnitsTracker:CheckBBoxCollision(crateBBox, objBBox, safetyMargin)
-- Apply safety margin to object bbox
local objMin = {
x = objBBox.min.x - safetyMargin,
@@ -563,7 +567,7 @@ function SupplyUnitsTracker:CheckBBoxCollision(crateBBox, objBBox, safetyMargin)
y = objBBox.max.y + safetyMargin,
z = objBBox.max.z + safetyMargin
}
-- AABB collision detection
return crateBBox.min.x <= objMax.x and crateBBox.max.x >= objMin.x and
crateBBox.min.y <= objMax.y and crateBBox.max.y >= objMin.y and
@@ -577,7 +581,7 @@ end
function SupplyUnitsTracker:GetCargoPlacePosition(unit, crateTypeName)
local unitPos = unit:getPosition()
-- Get unit's heading from the forward vector (x component)
-- Heading is calculated as: atan2(forward.z, forward.x)
local unitHeading = math.atan2(unitPos.x.z, unitPos.x.x)
@@ -588,14 +592,14 @@ function SupplyUnitsTracker:GetCargoPlacePosition(unit, crateTypeName)
self._logger:error("Could not get bbox for crate type: " .. crateTypeName)
return nil
end
local crateRelativeBBox = crateDesc.box
local crateRelativeBBox = crateDesc.box --[[@as table]]
-- Get drop zone config for this unit
local dropZones = {
{ centerAngle = 180, angleWidth = 60, minRadius = 15, maxRadius = 50, spacing = 5 }
}
if SupplyLoadConfig[unit:getTypeName()] ~= nil then
dropZones = SupplyLoadConfig[unit:getTypeName()].dropZones
end
@@ -612,9 +616,12 @@ function SupplyUnitsTracker:GetCargoPlacePosition(unit, crateTypeName)
radius = 100 -- Search a large area
}
}
---@type Array<{ pos: { x: number, y: number, z: number }, bbox: { min: { x: number, y: number, z: number }, max: { x: number, y: number, z: number } } }>
local occupiedObjects = {}
local found = function(foundItem, val)
---@param foundItem Object
---@param _ any
local found = function(foundItem, _)
local bbox = self:GetBoundingBoxes(foundItem)
if bbox then
self._logger:debug("Found object: " .. foundItem:getTypeName() .. " at (" .. foundItem:getPoint().x .. ", " .. foundItem:getPoint().z .. ")")
@@ -648,20 +655,21 @@ function SupplyUnitsTracker:GetCargoPlacePosition(unit, crateTypeName)
for distance = zone.minRadius, zone.maxRadius, zone.spacing do
-- Check multiple positions within the angular slice
local angleStep = math.min(15, zone.angleWidth / 3) -- Divide slice into sections
for angle = minAngle, maxAngle, angleStep do
local radians = math.rad(angle)
-- Calculate position at this angle and distance, relative to unit's heading
-- Angle 0 = forward, 90 = right, 180 = rear, 270 = left
-- Apply unit heading to make angles relative to unit orientation
local worldAngle = radians + unitHeading
local candidateX = unitPos.p.x + distance * math.sin(worldAngle)
local candidateZ = unitPos.p.z + distance * math.cos(worldAngle)
local candidateY = land.getHeight({ x = candidateX, y = candidateZ })
-- Convert crate's relative bbox to world space at this position
---@type { min: { x: number, y: number, z: number }, max: { x: number, y: number, z: number } }
local crateBBoxWorldSpace = {
min = {
x = candidateX + crateRelativeBBox.min.x,
@@ -25,7 +25,9 @@ local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelpe
local BuildableMission = {}
BuildableMission.__index = BuildableMission
---@param siteType string
---@param coords string
---@return string
local function getDefaultBriefing(siteType, coords)
return "We've dispatched forward units to find a proper spot for a new " .. siteType .. "." ..
"\nYou will need to drop off supplies so they can start building." ..
@@ -43,12 +45,13 @@ end
---@param noLandingZone SpearheadTriggerZone?
---@param logger Logger
---@param briefing string?
---@return BuildableMission
function BuildableMission.new(database, logger, targetZone, noLandingZone, requiredKilos, requiredCrateType, briefing)
setmetatable(BuildableMission, Mission)
local self = setmetatable({}, { __index = BuildableMission })
self._targetZone = targetZone
self._database = database
self._requiredKilos = requiredKilos
@@ -75,7 +78,7 @@ function BuildableMission.new(database, logger, targetZone, noLandingZone, requi
end
self.code = tostring(database:GetNewMissionCode())
local splitTargetZoneName = Util.split_string(targetZone.name, "_")
if splitTargetZoneName and splitTargetZoneName[3] and splitTargetZoneName[3] ~= "" then
self.name = splitTargetZoneName[3]
@@ -83,13 +86,6 @@ function BuildableMission.new(database, logger, targetZone, noLandingZone, requi
self.name = "Resupply"
end
local type = "site"
if requiredCrateType == "SAM_CRATE" then
type = "SAM site"
elseif requiredCrateType == "FARP_CRATE" then
type = "FARP"
end
self.zoneName = targetZone.name .. "_supply"
self._logger = logger
self._onCrateDroppedOfListeners = {}
@@ -124,6 +120,7 @@ function BuildableMission:ShowBriefing(groupID)
local unitType = DcsUtil.getUnitTypeFromGroup(group)
local coords = DcsUtil.convertVec2ToUnitUsableType(self.location, unitType)
if coords == nil then coords = "Could not make conversion" end
local siteType = "FARP"
if self._crateType == "SAM_CRATE" then
@@ -139,7 +136,7 @@ function BuildableMission:ShowBriefing(groupID)
briefingPart = getDefaultBriefing(siteType, coords)
end
local briefing = "Mission [" .. self.code .. "] " .. self.name ..
local briefing = "Mission [" .. self.code .. "] " .. self.name ..
"\n \n" ..
briefingPart ..
"\n\n" ..
@@ -150,17 +147,20 @@ function BuildableMission:ShowBriefing(groupID)
trigger.action.outTextForGroup(groupID, briefing, GlobalConfig:getBriefingTime())
end
---@param groupID number
function BuildableMission:MarkMissionAreaToGroup(groupID)
if self._markIDsPerGroup[groupID] then
DcsUtil.RemoveMark(self._markIDsPerGroup[groupID])
end
local groupIdStr = tostring(groupID)
if self._markIDsPerGroup[groupIdStr] then
DcsUtil.RemoveMark(self._markIDsPerGroup[groupIdStr])
end
---@type string
local text = "[" .. self.code .. "] " .. self.name .. " | " .. self._crateType
local location = { x= self.location.x, y=land.getHeight(self.location), z=self.location.y }
local markID = DcsUtil.AddMarkToGroup(groupID, text, location)
self._markIDsPerGroup[groupID] = markID
self._markIDsPerGroup[groupIdStr] = markID
end
---@private
@@ -201,9 +201,10 @@ function BuildableMission:SpawnActive()
local fillColor2 = DrawingHelper.ColorTableToColorString({ 0, 0, 1, 0})
self._dropOffZoneDrawing = CustomDrawing.FromZone(self._dropOffZone, lineColor2, fillColor2, 2, 6)
self._dropOffZoneDrawing:Draw()
---@param selfA BuildableMission
---@param time number
---@return number?
local checkForCrateTasks = function (selfA, time)
selfA:CheckCratesInZone()
@@ -266,8 +267,8 @@ function BuildableMission:CheckCratesInZone()
end
end
end
for _, foundCrate in pairs(foundCrates) do
for _, foundCrate in pairs(foundCrates) do
local crateConfig = SupplyConfigHelper.fromObjectName(foundCrate:getName())
if crateConfig then
self._droppedKilos = self._droppedKilos + crateConfig.weight
@@ -282,7 +283,7 @@ function BuildableMission:CheckCratesInZone()
self:NotifyMissionComplete()
self._state = "COMPLETED"
end
if self._state == "COMPLETED" then
for groupID, markID in pairs(self._markIDsPerGroup) do
if markID then
@@ -24,6 +24,7 @@ local RunwayStrikeMission = {}
---@param runway Runway
---@param database Database
---@param logger Logger
---@param airbaseName string
---@param runwayBombingTracker RunwayBombingTracker
---@return RunwayStrikeMission?
function RunwayStrikeMission.new(runway, airbaseName, database, logger, runwayBombingTracker)
@@ -42,7 +43,7 @@ function RunwayStrikeMission.new(runway, airbaseName, database, logger, runwayBo
self._runwayZone = self:RunwayToSpearheadZone(runway)
self._repairInProgress = false
self._minKilosForDamage = 100
local sections = self:ToSections(runway, 5)
--[[
+-----------+-----------+-----------+-----------+-----------+
@@ -84,7 +85,6 @@ function RunwayStrikeMission:RunwayHit(impactPoint, explosiveMass)
self._logger:debug("Runway hit: " .. self._airportName .. ":" .. self._runway.Name)
for _, section in pairs(self._runwaySections) do
local zone = self:SectionToSpearheadZone(section)
if Util.is3dPointInZone({ x = impactPoint.x, z = impactPoint.y, y = 0 }, zone) then
if section.kilosHit == nil then
@@ -96,7 +96,7 @@ function RunwayStrikeMission:RunwayHit(impactPoint, explosiveMass)
end
---@param selfA RunwayStrikeMission
local updateState = function(selfA, time)
local updateState = function(selfA, _)
selfA:UpdateState()
end
@@ -154,7 +154,7 @@ function RunwayStrikeMission:Draw()
if runwaySection.drawID == nil then
local zone = self:SectionToSpearheadZone(runwaySection)
---@type Free
local drawObject = {
primitiveType = "Polygon",
@@ -206,17 +206,18 @@ function RunwayStrikeMission:StartRepair()
self._repairInProgress = true
self._logger:debug("Starting repair of runway strike mission " .. self._airportName .. ":" .. self._runway.Name)
---comment
---@param selfA any
---@return unknown
---@param selfA RunwayStrikeMission
---@param time number
---@return number?
local repairTask = function (selfA, time)
local interval = selfA:DoRepairCycle()
if interval == nil then return nil end
return time + interval
end
timer.scheduleFunction(repairTask, self, timer.getTime() + 5)
end
---@return number?
function RunwayStrikeMission:DoRepairCycle()
local interval = 5
@@ -249,12 +250,14 @@ function RunwayStrikeMission:DoRepairCycle()
end
---@class StaticSpawn
---@class StaticSpawn : table
---@field category string
---@field type string
---@field y number
---@field x number
---@field heading number
---@field name string?
---@field hidden boolean?
local counter = 1
@@ -443,6 +446,7 @@ end
---@private
---@param runway Runway
---@param numSections number
---@return Array<RunwaySection>
function RunwayStrikeMission:ToSections(runway, numSections)
@@ -66,8 +66,6 @@ local function ParseZoneName(input)
}
end
MINIMAL_UNITS_ALIVE_RATIO = 0.21
---comment
---@param zoneName string
---@param priority MissionPriority
@@ -211,7 +209,7 @@ end
---@return boolean
function ZoneMission:AllDependenciesMet()
local allDependenciesMet = true
for missionName, value in pairs(self._dependencies) do
for missionName, _ in pairs(self._dependencies) do
if self._parentStage:IsMissionComplete(missionName) == false then
allDependenciesMet = false
self._dependencies[missionName] = false
@@ -229,10 +227,9 @@ end
---@internal
---@param checkHealth boolean
---@param messageIfDone boolean
function ZoneMission:UpdateState(checkHealth, messageIfDone)
---@param _ boolean
function ZoneMission:UpdateState(checkHealth, _)
if checkHealth == nil then checkHealth = false end
if messageIfDone == false then messageIfDone = true end
if checkHealth == true then
@@ -240,7 +237,7 @@ function ZoneMission:UpdateState(checkHealth, messageIfDone)
local staticObject = StaticObject.getByName(unitName)
if staticObject then
if staticObject:isExist() == true then
local life0 = staticObject:getDesc().life
local life0 = staticObject:getDesc().life --[[@as number]]
if staticObject:getLife() / life0 < 0.3 then
self._logger:debug("exploding unit")
trigger.action.explosion(staticObject:getPoint(), 100)
@@ -286,7 +283,9 @@ function ZoneMission:UpdateState(checkHealth, messageIfDone)
end
if self._missionGroups.hasTargets == true then
---@type number
local total = 0
---@type number
local alive = 0
for _, units in pairs(self._missionGroups.targetsAlive) do
@@ -299,13 +298,13 @@ function ZoneMission:UpdateState(checkHealth, messageIfDone)
end
for _, sceneryObject in pairs(self._missionGroups.sceneryTargets) do
total = total + 1
total = total + 1 --[[@as number]]
if sceneryObject:IsAlive() == true then
alive = alive + 1
end
end
local deadRatio = (total - alive) / total
local deadRatio = (total - alive) / total --[[@as number]]
if deadRatio >= self._completeAtIndex then
self._logger:debug("Dead ratio " .. self.zoneName .. deadRatio .. " >= " .. self._completeAtIndex)
self._state = "COMPLETED"
@@ -477,6 +476,7 @@ function ZoneMission:OnUnitLost(object)
local category = Object.getCategory(object)
if category == Object.Category.UNIT then
object = object --[[@as Unit]]
local unitName = object:getName()
self._logger:debug("UnitName:" .. unitName)
@@ -487,6 +487,7 @@ function ZoneMission:OnUnitLost(object)
self._missionGroups.targetsAlive[groupName][unitName] = false
end
elseif category == Object.Category.STATIC then
object = object --[[@as StaticObject]]
local name = object:getName()
self._missionGroups.unitsAlive[name][name] = false
@@ -503,12 +504,12 @@ end
---@param unit Object
function ZoneMission:MarkLastContact(unit)
if not unit then
if not unit then
self._logger:error("MarkLastContact called with nil unit")
return
end
local point = unit:getPoint()
local point = unit:getPoint()
if not point then
self._logger:error("MarkLastContact called with unit without point")
return
@@ -51,7 +51,7 @@ function Mission.newSuper(self, zoneName, missionName, missionType, missionBrief
self.location = database:GetLocationForMissionZone(zoneName)
self.missionTypeDisplay = self.missionType
self._missionCommandsHelper = MissionCommandsHelper.getOrCreate()
return true, "success"
@@ -68,9 +68,10 @@ function Mission:SpawnPersistedState() end
function Mission:SpawnActive() end
---comment
---@param checkHealth boolean
---@param messageIfDone boolean
function Mission:UpdateState(checkHealth, messageIfDone) end
---@param _checkHealth boolean
---@param _messageIfDone boolean
---@diagnostic disable-next-line: unused-local
function Mission:UpdateState(_checkHealth, _messageIfDone) end
function Mission:StartCheckingContinuous() end
function Mission:PercentageComplete()
@@ -85,6 +86,7 @@ function Mission:ShowBriefing(groupId)
local unitType = DcsUtil.getUnitTypeFromGroup(group)
local coords = DcsUtil.convertVec2ToUnitUsableType(self.location, unitType)
if coords == nil then coords = "Could not make conversion" end
self._logger:debug("Coords converted: " .. coords)
local stateString = self:ToStateString()
@@ -120,7 +122,7 @@ function Mission:NotifyMissionComplete()
end)
end
local succ, err = pcall(function()
local _, _ = pcall(function()
SpearheadAPI.Internal.notifyMissionComplete(self.zoneName)
end)
@@ -131,7 +133,9 @@ function Mission:ForceMissionComplete()
self:NotifyMissionComplete()
end
function Mission:MarkMissionAreaToGroup(groupId) end
---@param _groupId number
---@diagnostic disable-next-line: unused-local
function Mission:MarkMissionAreaToGroup(_groupId) end
---endregion
@@ -143,29 +147,26 @@ function Mission:ToStateString() return "status: in progress" end
--endregion
do --aliases
--- @alias MissionPriority
--- | "none"
--- | "primary"
--- | "secondary"
--- @alias MissionPriority
--- | "none"
--- | "primary"
--- | "secondary"
--- @alias MissionType
--- | "nil"
--- | "STRIKE"
--- | "CAS"
--- | "BAI"
--- | "DEAD"
--- | "SAM"
--- | "OCA"
--- | "LOGISTICS"
--- @alias MissionType
--- | "nil"
--- | "STRIKE"
--- | "CAS"
--- | "BAI"
--- | "DEAD"
--- | "SAM"
--- | "OCA"
--- | "LOGISTICS"
--- @alias MissionState
--- | "NEW"
--- | "WAITING"
--- | "ACTIVE"
--- | "COMPLETED"
--- @alias MissionState
--- | "NEW"
--- | "WAITING"
--- | "ACTIVE"
--- | "COMPLETED"
end
return Mission