Feature/mission refactor (#26)

* Refactored Mission classes to add Missions other than trigger zone missions. (eg. Runway Strikes, Resupply etc.)
* Runway strike initial POC
Co-authored-by: dutchie032 <dutchie032>
This commit is contained in:
2025-05-07 13:18:45 +02:00
committed by GitHub
parent 31f99686f6
commit a2f62d797b
30 changed files with 1580 additions and 547 deletions
@@ -0,0 +1,543 @@
---@class RunwayStrikeMission : Mission
---@field runwayBombingTracker RunwayBombingTracker
---@field private _runway Runway
---@field private _runwayZone SpearheadTriggerZone
---@field private _airportName string
---@field private _runwaySections Array<RunwaySection>
---@field private _minKilosForDamage number
---@field private _repairInProgress boolean
local RunwayStrikeMission = {}
---@class RunwaySection
---@field center Vec2
---@field corners Array<Vec2>
---@field kilosHit number
---@field repairGroups Array<string>
---@field drawID number?
---@param runway Runway
---@param database Database
---@param logger Logger
---@param runwayBombingTracker RunwayBombingTracker
---@return RunwayStrikeMission?
function RunwayStrikeMission.new(runway, airbaseName, database, logger, runwayBombingTracker)
local Mission = Spearhead.classes.stageClasses.missions.baseMissions.Mission
RunwayStrikeMission.__index = RunwayStrikeMission
setmetatable(RunwayStrikeMission, Mission)
local self = setmetatable({}, RunwayStrikeMission)
self._airportName = airbaseName
local missionBriefing = "Bomb runway " .. runway.Name .. " at " .. airbaseName .. "to delay the CAP effort"
local success, error = Mission.newSuper(self, "noZone", runway.Name, "OCA", missionBriefing, "secondary", database, logger)
self.runwayBombingTracker = runwayBombingTracker
self._runway = runway
self._runwayZone = self:RunwayToSpearheadZone(runway)
self._repairInProgress = false
self._minKilosForDamage = 100
local sections = self:ToSections(runway, 5)
--[[
+-----------+-----------+-----------+-----------+-----------+
| Section 1 | Section 2 | Section 3 | Section 4 | Section 5 |
+-----------+-----------+-----------+-----------+-----------+
]]
self._runwaySections = {
sections[2],
sections[3],
sections[4],
} --only take 2, 3 and 4 cause those are the most imporant parts of the runway
if not success then
logger:error("Failed to create RunwayBombingMission " .. runway.Name .. " => " .. error)
return nil
end
runwayBombingTracker:RegisterRunway(runway, self)
return self
end
---activates the mission
function RunwayStrikeMission:SpawnActive()
self._missionCommandsHelper:AddMissionToCommands(self)
self:Draw()
self:UpdateState()
end
---@return SpearheadTriggerZone
function RunwayStrikeMission:GetRunwayZone()
return self._runwayZone
end
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 Spearhead.Util.is3dPointInZone({ x = impactPoint.x, z = impactPoint.y, y = 0 }, zone) then
if section.kilosHit == nil then
section.kilosHit = 0
end
section.kilosHit = section.kilosHit + explosiveMass
end
end
---@param selfA RunwayStrikeMission
local updateState = function(selfA, time)
selfA:UpdateState()
end
timer.scheduleFunction(updateState, self, timer.getTime() + 5)
end
function RunwayStrikeMission:UpdateState()
self._logger:debug("Updating state of runway strike mission " .. self._airportName .. ":" .. self._runway.Name)
self:Draw()
for _, section in pairs(self._runwaySections) do
if section.kilosHit > self._minKilosForDamage then
self:StartRepair()
self._missionCommandsHelper:RemoveMissionToCommands(self)
break
end
end
end
---@type DrawColor
local healthyAreaColor = { r=0, g=1, b=0, a=0.5 }
---@type DrawColor
local healthyAreaLineColor = { r=0, g=1, b=0, a=1 }
---@type DrawColor
local damagedAreaColor ={ r=1, g=165/255, b=0, a=0.5 }
---@type DrawColor
local damagedAreaLineColor = { r=1, g=165/255, b=0, a=1 }
---@type DrawColor
local destroyedAreaColor = { r=1, g=0, b=0, a=0.5 }
---@type DrawColor
local destroyedAreaLineColor = { r=1, g=0, b=0, a=1 }
---@private
function RunwayStrikeMission:Draw()
---comment
---@param runwaySection RunwaySection
local function drawSection(runwaySection)
local lineColor = healthyAreaLineColor
local fillColor = healthyAreaColor
local orangeDamage = self._minKilosForDamage - 100
if orangeDamage < 0 then orangeDamage = self._minKilosForDamage * 0.8 end
if runwaySection.kilosHit > self._minKilosForDamage then
lineColor = destroyedAreaLineColor
fillColor = destroyedAreaColor
elseif runwaySection.kilosHit > orangeDamage then
lineColor = damagedAreaLineColor
fillColor = damagedAreaColor
end
if runwaySection.drawID == nil then
local zone = self:SectionToSpearheadZone(runwaySection)
local color = { r=0, g=1, b=0, a=0.5 }
runwaySection.drawID = Spearhead.DcsUtil.DrawZone(zone, color, color, 5)
else
Spearhead.DcsUtil.SetFillColor(runwaySection.drawID, fillColor)
Spearhead.DcsUtil.SetLineColor(runwaySection.drawID, lineColor)
end
end
for _, section in pairs(self._runwaySections) do
drawSection(section)
end
end
---@private
---@param section RunwaySection
---@return SpearheadTriggerZone
function RunwayStrikeMission:SectionToSpearheadZone(section)
return {
location = { x = self._runway.position.x, y = self._runway.position.z },
radius = self._runway.width,
name = self._runway.Name,
verts = section.corners,
zone_type = "Polygon",
}
end
---@
function RunwayStrikeMission:StartRepair()
if self._repairInProgress == true then return end
self._repairInProgress = true
self._logger:debug("Starting repair of runway strike mission " .. self._airportName .. ":" .. self._runway.Name)
---comment
---@param selfA any
---@return unknown
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
function RunwayStrikeMission:DoRepairCycle()
local interval = 5
local repairPerSecond = 5
self._logger:debug("Repair cycle for" .. self._airportName .. ":" .. self._runway.Name)
local isHealed = true
for _, section in pairs(self._runwaySections) do
section.kilosHit = section.kilosHit - interval * repairPerSecond
if section.kilosHit > self._minKilosForDamage * 0.1 then
self:AddOrUpdateRepairStatics(section)
isHealed = false
else
self:RemoveRepairStatics(section)
end
end
self:Draw()
if isHealed == true then
self._repairInProgress = false
self:FullRepairRunway()
self._logger:debug("Repair complete for runway strike mission " .. self._airportName .. ":" .. self._runway.Name)
return nil
end
return interval
end
---@class StaticSpawn
---@field category string
---@field type string
---@field y number
---@field x number
---@field heading number
local counter = 1
---@type Array<Array<StaticSpawn>>
local repairStaticConfigs = {
[1] = {
[1] = {
["category"] = "Unarmed",
["type"] = "ZIL-135",
["y"] = -4,
["x"] = 10,
["heading"] = 6.2133721370998,
},
[2] = {
["category"] = "Unarmed",
["type"] = "Tigr_233036",
["y"] = 0,
["x"] = 10,
["heading"] = 6.2133721370998,
},
[3] = {
["category"] = "Unarmed",
["type"] = "Infantry AK ver3",
["y"] = 10,
["x"] = 7,
["heading"] = 4.4331363000656,
},
[4] = {
["category"] = "Unarmed",
["type"] = "Infantry AK ver3",
["y"] = 12,
["x"] = 1,
["heading"] = 4.4331363000656,
},
[5] = {
["category"] = "Unarmed",
["type"] = "Infantry AK ver3",
["y"] = -10,
["x"] = -2,
["heading"] = 4.1538836197465,
},
[6] = {
["category"] = "Unarmed",
["type"] = "CV_59_Large_Forklift",
["y"] = -11,
["x"] = 0,
["heading"] = 1.535889741755,
},
[7] = {
["category"] = "Unarmed",
["type"] = "ZiL-131 APA-80",
["y"] = 11,
["x"] = 5,
["heading"] = 0.62831853071796,
},
[8] = {
["category"] = "Unarmed",
["type"] = "CV_59_NS60",
["y"] = -4,
["x"] = -15,
["heading"] = 0.62831853071796,
}
},
[2] = {
[1] = {
["category"] = "Air Defence",
["type"] = "generator_5i57",
["y"] = 2.7889551542015,
["x"] = -4.7698247930386,
["heading"] = 4.0666171571468,
},
[2] = {
["category"] = "Infantry",
["type"] = "Infantry AK ver3",
["y"] = 3.639392285097,
["x"] = 1.4300755972836,
["heading"] = 5.0440015382636,
},
[3] = {
["category"] = "Infantry",
["type"] = "Infantry AK ver3",
["y"] = 3.5139072826724,
["x"] = -0.32671443666074,
["heading"] = 6.16101225954,
},
[4] = {
["category"] = "Unarmed",
["type"] = "ATMZ-5",
["y"] = -6.9556010010953,
["x"] = 3.007681753102,
["heading"] = 4.0666171571468,
},
[5] = {
["category"] = "Unarmed",
["type"] = "GAZ-66",
["y"] = 5.9304017026008,
["x"] = 0.80323129595153,
["heading"] = 6.2308254296198,
}
}
}
---@private
---@param section RunwaySection
function RunwayStrikeMission:AddOrUpdateRepairStatics(section)
if Spearhead.Util.tableLength(section.repairGroups) > 0 then
return
end
local location = { --Can randomise a little later
x = section.center.x,
y = section.center.y,
}
local repairGroup = Spearhead.Util.randomFromList(repairStaticConfigs)
for _, repairStatic in pairs(repairGroup) do
repairStatic.x = location.x + repairStatic.x
repairStatic.y = location.y + repairStatic.y
repairStatic.hidden = true
repairStatic.name = "runway_repairunit_" .. counter
counter = counter + 1
coalition.addStaticObject(country.id.RUSSIA, repairStatic)
table.insert(section.repairGroups, repairStatic.name)
end
end
---@private
---@param section RunwaySection
function RunwayStrikeMission:RemoveRepairStatics(section)
if Spearhead.Util.tableLength(section.repairGroups) == 0 then
return
end
for _, repairGroup in pairs(section.repairGroups) do
local static = StaticObject.getByName(repairGroup)
if static then
static:destroy()
end
end
section.repairGroups = {}
end
---@private
function RunwayStrikeMission:FullRepairRunway()
for _, section in pairs(self._runwaySections) do
section.kilosHit = 0
self:RemoveRepairStatics(section)
end
local minX = self._runwayZone.verts[1].x
local minY = self._runwayZone.verts[1].y
local maxX = self._runwayZone.verts[1].x
local maxY = self._runwayZone.verts[1].y
for _, vert in pairs(self._runwayZone.verts) do
if vert.x < minX then
minX = vert.x
end
if vert.x > maxX then
maxX = vert.x
end
if vert.y < minY then
minY = vert.y
end
if vert.y > maxY then
maxY = vert.y
end
end
---@type Box
local box = {
id = world.VolumeType.BOX,
params = {
min = { x = minX, z = minY, y = 0 },
max = { x = maxX, z = maxY, y = 10000 },
}
}
world.removeJunk(box)
end
---@private
---@param runway Runway
---@return Array<RunwaySection>
function RunwayStrikeMission:ToSections(runway, numSections)
local sections = {}
local center = runway.position
local heading = runway.course
local width = runway.width
local length = runway.length / numSections
if heading < 0 then
heading = math.abs(heading)
else
heading = 0 - heading
end
local cosH = math.cos(heading)
local sinH = math.sin(heading)
for i = 0, numSections - 1 do
local sectionCenterOffset = (i - (numSections / 2) + 0.5) * length
---@type Vec2
local sectionCenter = {
x = center.x + sectionCenterOffset * cosH,
y = center.z + sectionCenterOffset * sinH
}
local halfWidth = width / 2
local halfLength = length / 2
---@type Array<Vec2>
local corners = {
{
x = sectionCenter.x + (-halfLength * cosH - halfWidth * sinH),
y = sectionCenter.y + (-halfLength * sinH + halfWidth * cosH)
},
{
x = sectionCenter.x + (-halfLength * cosH + halfWidth * sinH),
y = sectionCenter.y + (-halfLength * sinH - halfWidth * cosH)
},
{
x = sectionCenter.x + (halfLength * cosH + halfWidth * sinH),
y = sectionCenter.y + (halfLength * sinH - halfWidth * cosH)
},
{
x = sectionCenter.x + (halfLength * cosH - halfWidth * sinH),
y = sectionCenter.y + (halfLength * sinH + halfWidth * cosH)
}
}
---@type RunwaySection
local section = {
center = sectionCenter,
corners = corners,
kilosHit = 0,
repairGroups = {},
}
table.insert(sections, section)
end
return sections
end
---@private
---@param runway Runway
---@return SpearheadTriggerZone
function RunwayStrikeMission:RunwayToSpearheadZone(runway)
-- Calculate the 4 corner points of the runway based on heading, height, and width
local radHeading = runway.course
if radHeading < 0 then
radHeading = math.abs(radHeading)
else
radHeading = 0 - radHeading
end
local cosH = math.cos(radHeading)
local sinH = math.sin(radHeading)
local halfWidth = runway.width / 2
local halfHeight = runway.length / 2
---@type Array<Vec2>
local corners = {
{
x = runway.position.x + (-halfHeight * cosH - halfWidth * sinH),
y = runway.position.z + (-halfHeight * sinH + halfWidth * cosH)
},
{
x = runway.position.x + (-halfHeight * cosH + halfWidth * sinH),
y = runway.position.z + (-halfHeight * sinH - halfWidth * cosH)
},
{
x = runway.position.x + (halfHeight * cosH + halfWidth * sinH),
y = runway.position.z + (halfHeight * sinH - halfWidth * cosH)
},
{
x = runway.position.x + (halfHeight * cosH - halfWidth * sinH),
y = runway.position.z + (halfHeight * sinH + halfWidth * cosH)
}
}
return {
location = { x = runway.position.x, y = runway.position.z },
radius = runway.width,
name = runway.Name,
verts = corners,
zone_type = "Polygon",
}
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.RunwayStrikeMission = RunwayStrikeMission
@@ -0,0 +1,361 @@
--- ZoneMission is missions that are defined by zones in the ME
---@class ZoneMission : Mission, OnUnitLostListener
---@field private _missionGroups MissionGroups
local ZoneMission = {}
--- @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>
---@class ParsedMissionName
---@field missionName string
---@field type MissionType
---comment
---@param input string
---@return ParsedMissionName?
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
MINIMAL_UNITS_ALIVE_RATIO = 0.21
---comment
---@param zoneName string
---@param priority MissionPriority
---@param database Database
---@param logger Logger
---@return ZoneMission?
function ZoneMission.new(zoneName, priority, database, logger)
local Mission = Spearhead.classes.stageClasses.missions.baseMissions.Mission
ZoneMission.__index = ZoneMission
setmetatable(ZoneMission, Mission)
local self = setmetatable({}, ZoneMission)
local parsed = ParseZoneName(zoneName)
if not parsed then
logger:error("Failed to create ZoneMission " .. zoneName .. " => invalid name")
return nil
end
local missionBriefing = database:getMissionBriefingForMissionZone(zoneName) or "no briefing provided"
local success, error = Mission.newSuper(self, zoneName, parsed.missionName, parsed.type, missionBriefing, priority, database, logger)
if not success then
logger:error("Failed to create ZoneMission " .. zoneName .. " => " .. error)
return nil
end
if self.missionType == "SAM" then
self.missionTypeDisplay = "DEAD"
end
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
self._logger:debug("Mission " .. self.name .. " group count: " .. Spearhead.Util.tableLength(groupNames))
return self
end
---@internal
---@param checkHealth boolean
---@param messageIfDone boolean
function ZoneMission: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)
if unit and unit:isExist() 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
end
function ZoneMission:SpawnPersistedState()
for _, group in pairs(self._missionGroups.groups) do
group:SpawnCorpsesOnly()
end
end
---spawns the mission, but doesn't add
function ZoneMission:SpawnInactive()
self._logger:info("PreActivating " .. self.name)
self._state = "ACTIVE"
for _, group in pairs(self._missionGroups.groups) do
group:Spawn()
end
end
function ZoneMission:SpawnActive()
self._logger:info("Activating " .. self.name)
self._state = "ACTIVE"
for _, group in pairs(self._missionGroups.groups) do
group:Spawn()
end
self._missionCommandsHelper:AddMissionToCommands(self)
self:StartCheckingContinuous()
end
---@private
function ZoneMission: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
---@protected
function ZoneMission: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
function ZoneMission: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.ZoneMission = ZoneMission
@@ -0,0 +1,147 @@
---@class Mission
---@field name string
---@field zoneName string
---@field missionType MissionType
---@field missionTypeDisplay string
---@field priority MissionPriority
---@field missionBriefing string
---@field location Vec2?
---@field code string
---@field protected state MissionState
---@field getState fun(self: Mission): MissionState @Get the mission state
---@field protected _logger Logger
---@field protected _database Database
---@field protected _missionCommandsHelper MissionCommandsHelper
---@field protected _completeListeners Array<MissionCompleteListener>
local Mission = {}
Mission.__index = Mission
--- @class MissionCompleteListener
--- @field OnMissionComplete fun(self: any, mission:Mission)
---@protected
---@param self Mission
---@param zoneName string
---@param missionName string
---@param missionType MissionType
---@param missionBriefing string
---@param priority MissionPriority
---@param database Database
---@param logger Logger
---@return boolean, string
function Mission.newSuper(self, zoneName, missionName, missionType, missionBriefing, priority, database, logger)
self.zoneName = zoneName
self.name = missionName
self.missionType = missionType
self.priority = priority
self.state = "NEW"
self._logger = logger
self._database = database
self.missionBriefing = missionBriefing
self.code = tostring(database:GetNewMissionCode())
self._completeListeners = {}
self.location = database:GetLocationForMissionZone(zoneName)
self.missionTypeDisplay = self.missionType
self._missionCommandsHelper = Spearhead.classes.stageClasses.helpers.MissionCommandsHelper.getOrCreate(logger.LogLevel)
return true, "success"
end
---@return MissionState
function Mission:getState()
return self.state
end
--region PUBLIC
function Mission:SpawnPersistedState() end
function Mission:SpawnActive() end
---comment
---@param checkHealth boolean
---@param messageIfDone boolean
function Mission:UpdateState(checkHealth, messageIfDone) end
function Mission:StartCheckingContinuous() end
---comment
---@param groupId number
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 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:NotifyMissionComplete()
self._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
---endregion
--region PROTECTED
---@protected
function Mission:ToStateString() return "status: in progress" end
--endregion
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
if not Spearhead.classes.stageClasses.missions.baseMissions then Spearhead.classes.stageClasses.missions.baseMissions = {} end
Spearhead.classes.stageClasses.missions.baseMissions.Mission = Mission
do --aliases
--- @alias MissionPriority
--- | "none"
--- | "primary"
--- | "secondary"
--- @alias MissionType
--- | "nil"
--- | "STRIKE"
--- | "BAI"
--- | "DEAD"
--- | "SAM"
--- | "OCA"
--- @alias MissionState
--- | "NEW"
--- | "ACTIVE"
--- | "COMPLETED"
end