Cap rework (#48)
- Refactored all CAP classes. - Improved timing - Zones are now not tied to MissionStages - CAPROUTE_<routeID>_<free> - routeID is now what is used in the naming convention. - Units taxi back to their spawn parking space and rearm there. (Units destroyed while in the Rearming or Repairing state are not taken into account)
This commit is contained in:
@@ -0,0 +1,427 @@
|
||||
---@class AirGroup : OnUnitLostListener
|
||||
---@field protected _logger Logger
|
||||
---@field protected _groupName string
|
||||
---@field protected _groupType AirGroupType
|
||||
---@field protected _state AirGroupState
|
||||
---@field protected _isSpawned boolean
|
||||
---@field protected _config CapConfig
|
||||
---@field protected _checkLivenessNumber number
|
||||
local AirGroup = {}
|
||||
AirGroup.__index = AirGroup
|
||||
|
||||
---@param logger Logger
|
||||
---@param groupName string
|
||||
---@param groupType AirGroupType
|
||||
---@param config CapConfig
|
||||
function AirGroup:New(groupName, groupType, config, logger)
|
||||
self._groupName = groupName
|
||||
self._groupType = groupType
|
||||
self._isSpawned = false
|
||||
self._state = "UnSpawned" -- Default state
|
||||
self._config = config
|
||||
self._logger = logger
|
||||
|
||||
local group = Group.getByName(self._groupName)
|
||||
if group then
|
||||
Spearhead.Events.addOnGroupRTBListener(self._groupName, self)
|
||||
Spearhead.Events.addOnGroupRTBInTenListener(self._groupName, self)
|
||||
Spearhead.Events.addOnGroupOnStationListener(self._groupName, self)
|
||||
|
||||
for _, unit in pairs(group:getUnits()) do
|
||||
Spearhead.Events.addOnUnitLostEventListener(unit:getName(), self)
|
||||
Spearhead.Events.addOnUnitLandEventListener(unit:getName(), self)
|
||||
end
|
||||
Spearhead.DcsUtil.DestroyGroup(self._groupName)
|
||||
end
|
||||
end
|
||||
|
||||
function AirGroup:GetState()
|
||||
return self._state
|
||||
end
|
||||
|
||||
function AirGroup:GetName()
|
||||
return self._groupName
|
||||
end
|
||||
|
||||
function AirGroup:MarkRearmComplete()
|
||||
self:Respawn(false)
|
||||
if self._state == "Rearming" then
|
||||
self:SetState("ReadyOnTheRamp")
|
||||
end
|
||||
end
|
||||
|
||||
---@protected
|
||||
function AirGroup:SetMission(mission)
|
||||
self:SetState("InTransit")
|
||||
|
||||
local group = Group.getByName(self._groupName)
|
||||
if group then
|
||||
local controller = group:getController()
|
||||
if controller then
|
||||
controller:setCommand({
|
||||
id = 'Start',
|
||||
params = {}
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
local setMissionDelayed = function(data, time)
|
||||
data.self:SetMissionPrivate(data.mission)
|
||||
end
|
||||
|
||||
local data = {
|
||||
self = self,
|
||||
mission = mission
|
||||
}
|
||||
|
||||
timer.scheduleFunction(setMissionDelayed, data, timer.getTime() + 5)
|
||||
end
|
||||
|
||||
function AirGroup:SetMissionPrivate(mission)
|
||||
self:SetState("InTransit")
|
||||
local group = Group.getByName(self._groupName)
|
||||
if group and mission then
|
||||
group:getController():setTask(mission)
|
||||
self._logger:debug("mission - Task set for group: " .. self._groupName)
|
||||
end
|
||||
end
|
||||
|
||||
---@param airbase Airbase
|
||||
function AirGroup:SendRTB(airbase)
|
||||
self._logger:debug("AirGroup:SendRTB called for group: " .. self._groupName)
|
||||
self:SetState("Rtb")
|
||||
local group = Group.getByName(self._groupName)
|
||||
if group then
|
||||
---@type Vec3
|
||||
local location = nil
|
||||
for _, unit in pairs(group:getUnits()) do
|
||||
if unit and unit:isExist() == true and unit:inAir() == true then
|
||||
location = unit:getPoint()
|
||||
break
|
||||
end
|
||||
end
|
||||
if location then
|
||||
local mission = Spearhead.classes.capClasses.taskings.RTB.getAsMission(airbase, { x= location.x, y= location.z }, self._config)
|
||||
group:getController():setTask(mission)
|
||||
self._logger:debug("AirGroup:SendRTB - Task set for group: " .. self._groupName)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function AirGroup:Spawn()
|
||||
if self._isSpawned then return end
|
||||
self:SpawnInternal(false)
|
||||
end
|
||||
|
||||
---@param force boolean
|
||||
---@param withoutLoadout boolean?
|
||||
---@protected
|
||||
function AirGroup:SpawnInternal(force, withoutLoadout)
|
||||
|
||||
if withoutLoadout == nil then withoutLoadout = false end
|
||||
|
||||
if self._isSpawned and force ~= true then return end
|
||||
|
||||
local group, isStatic = Spearhead.DcsUtil.SpawnGroupTemplate(self._groupName, nil, nil, true, nil, withoutLoadout)
|
||||
if isStatic == true then
|
||||
--- If For Some reaons someone tries to schedule static units as CAP classes
|
||||
self._state = "UnSpawned"
|
||||
return
|
||||
end
|
||||
|
||||
if group then
|
||||
self._group = group
|
||||
self._isSpawned = true
|
||||
self._initialSize = #group:getUnits()
|
||||
self._liveState = {}
|
||||
else
|
||||
self._logger:error("Failed to spawn group: " .. self._groupName)
|
||||
end
|
||||
|
||||
if self._state == "UnSpawned" then
|
||||
self:SetState("ReadyOnTheRamp")
|
||||
end
|
||||
|
||||
---@param selfA AirGroup
|
||||
local function CheckLivenessTask(selfA, time)
|
||||
local interval = selfA:CheckLiveness()
|
||||
if not interval then return end
|
||||
return time + interval
|
||||
end
|
||||
|
||||
if self._checkLivenessNumber then
|
||||
timer.removeFunction(self._checkLivenessNumber)
|
||||
end
|
||||
|
||||
self._checkLivenessNumber = timer.scheduleFunction(CheckLivenessTask, self, timer.getTime() + 5)
|
||||
end
|
||||
|
||||
|
||||
---@param withoutLoadout boolean?
|
||||
function AirGroup:Respawn(withoutLoadout)
|
||||
self:SpawnInternal(true, withoutLoadout)
|
||||
end
|
||||
|
||||
---@protected
|
||||
---@param state AirGroupState
|
||||
function AirGroup:SetState(state)
|
||||
if self._state == state then return end
|
||||
self._state = state
|
||||
self._logger:debug("AirGroup:State changed for group: " .. self._groupName .. " to state: " .. state)
|
||||
end
|
||||
|
||||
---@return number? timeInterval
|
||||
function AirGroup:CheckLiveness()
|
||||
local isAlive = false
|
||||
local group = Group.getByName(self._groupName)
|
||||
|
||||
if group and group:isExist() == true then
|
||||
for _, unit in pairs(group:getUnits()) do
|
||||
if unit and unit:isExist() == true and unit:getLife() > (unit:getLife0() * 0.3) then
|
||||
isAlive = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if isAlive == false then
|
||||
self:SetState("Dead")
|
||||
self:CheckStateAndStartRepairRearm()
|
||||
return nil
|
||||
end
|
||||
|
||||
return 10
|
||||
end
|
||||
|
||||
---@protected
|
||||
function AirGroup:OnLastUnitLanded()
|
||||
--- Once landed monitor the units until it's at it's designated location (or died)
|
||||
|
||||
-- Units
|
||||
|
||||
---@class CheckGroupForRestartData
|
||||
---@field self AirGroup
|
||||
---@field lastLocations table<string,Vec3>
|
||||
---@field lastChangeTime number
|
||||
|
||||
---@param data CheckGroupForRestartData
|
||||
---@param time number
|
||||
local checkGroupForRestart = function(data, time)
|
||||
local group = Group.getByName(data.self:GetName())
|
||||
if not group then
|
||||
self:CheckStateAndStartRepairRearm()
|
||||
return
|
||||
end
|
||||
|
||||
for _, unit in pairs(group:getUnits()) do
|
||||
if unit and unit:isExist() then
|
||||
local pos = unit:getPoint()
|
||||
|
||||
|
||||
if data.lastLocations[unit:getName()] == nil then
|
||||
data.lastLocations[unit:getName()] = pos
|
||||
return time + 5
|
||||
end
|
||||
|
||||
if pos and Spearhead.Util.VectorDistance3d(pos, data.lastLocations[unit:getName()]) > 10 then
|
||||
data.lastChangeTime = time
|
||||
end
|
||||
data.lastLocations[unit:getName()] = pos
|
||||
end
|
||||
end
|
||||
|
||||
if data.lastChangeTime + 30 < time then
|
||||
-- If no change in 30 seconds, assume all units are parked
|
||||
local withoutLoadout = true
|
||||
data.self:Respawn(withoutLoadout)
|
||||
data.self:CheckStateAndStartRepairRearm()
|
||||
return
|
||||
end
|
||||
|
||||
return time + 5
|
||||
end
|
||||
|
||||
---@type CheckGroupForRestartData
|
||||
local data = {
|
||||
self = self,
|
||||
lastLocations = {},
|
||||
lastChangeTime = timer.getTime()
|
||||
}
|
||||
|
||||
local group = Group.getByName(self._groupName)
|
||||
if group then
|
||||
for _, unit in pairs(group:getUnits()) do
|
||||
data.lastLocations[unit:getName()] = unit:getPoint()
|
||||
end
|
||||
end
|
||||
timer.scheduleFunction(checkGroupForRestart, data, timer.getTime() + 5)
|
||||
self._logger:debug("AirGroup:OnLastUnitLanded - Monitoring group: " .. self._groupName)
|
||||
end
|
||||
|
||||
function AirGroup:CheckStateAndStartRepairRearm()
|
||||
self._logger:debug("AirGroup:CheckStateAndStartRepairRearm called for group: " .. self._groupName)
|
||||
local group = Group.getByName(self._groupName)
|
||||
local anyAlive = false
|
||||
local allAlive = true
|
||||
|
||||
if group then
|
||||
for _, unit in pairs(group:getUnits()) do
|
||||
if unit and unit:isExist() == true and unit:getLife() > (unit:getLife0() * 0.3) then
|
||||
anyAlive = true
|
||||
else
|
||||
allAlive = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if anyAlive == false then
|
||||
-- Schedule Spawn + Repair + Rearm
|
||||
self:StartRespawn()
|
||||
return
|
||||
end
|
||||
|
||||
if allAlive == false then
|
||||
--- Schedule Spawn + Repair + Rearm
|
||||
self:StartRepair()
|
||||
return
|
||||
end
|
||||
|
||||
--- Reschedule Spawn + Rearm
|
||||
self:StartRearm()
|
||||
end
|
||||
|
||||
do --- RESPAWN FUNCTIONS
|
||||
--[[
|
||||
TODO: Checks to be added to the functions in case a group is destroyed while waiting for repair/rearm.
|
||||
]]
|
||||
|
||||
function AirGroup:StartRespawn()
|
||||
self:SetState("Dead")
|
||||
|
||||
local respawnTask = function(selfA, time)
|
||||
selfA:RepairDelayed()
|
||||
end
|
||||
|
||||
local delay = self._config:getDeathDelay()
|
||||
if delay < 2 then
|
||||
delay = 2
|
||||
end
|
||||
return timer.scheduleFunction(respawnTask, self, timer.getTime() + delay)
|
||||
end
|
||||
|
||||
function AirGroup:StartRepair()
|
||||
self:SetState("Repairing")
|
||||
|
||||
if self._isSpawned == false then
|
||||
self:Spawn()
|
||||
end
|
||||
|
||||
local rearmTask = function(selfA, time)
|
||||
self:StartRearm()
|
||||
end
|
||||
|
||||
local delay = self._config:getRepairDelay()
|
||||
if delay < 2 then
|
||||
delay = 2
|
||||
end
|
||||
|
||||
return timer.scheduleFunction(rearmTask, self, timer.getTime() + delay)
|
||||
end
|
||||
|
||||
function AirGroup:StartRearm()
|
||||
self:SetState("Rearming")
|
||||
|
||||
if self._isSpawned == false then
|
||||
self:Spawn()
|
||||
end
|
||||
|
||||
local rearmDelay = self._config:getRearmDelay()
|
||||
if rearmDelay < 2 then
|
||||
rearmDelay = 2
|
||||
end
|
||||
|
||||
---@param selfA AirGroup
|
||||
local rearmTask = function(selfA, time)
|
||||
selfA:MarkRearmComplete()
|
||||
end
|
||||
|
||||
-- Schedule Rearm Complete
|
||||
return timer.scheduleFunction(rearmTask, self, timer.getTime() + rearmDelay)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
do --EVENT LISTENERS
|
||||
---@param unit Unit
|
||||
function AirGroup:OnUnitLost(unit)
|
||||
self:CheckLiveness()
|
||||
end
|
||||
|
||||
---@param groupName string
|
||||
function AirGroup:OnGroupRTBInTen(groupName)
|
||||
if self._groupName == groupName then
|
||||
self._logger:debug("AirGroup:OnGroupRTBInTen called for group: " .. self._groupName)
|
||||
self:SetState("RtbInTen")
|
||||
end
|
||||
end
|
||||
|
||||
---@param groupName string
|
||||
function AirGroup:OnGroupRTB(groupName)
|
||||
if self._groupName == groupName then
|
||||
self._logger:debug("AirGroup:OnGroupRTB called for group: " .. self._groupName)
|
||||
self:SetState("Rtb")
|
||||
end
|
||||
end
|
||||
|
||||
function AirGroup:OnUnitLanded(unit, airbase)
|
||||
local anyInAir = false
|
||||
local group = Group.getByName(self._groupName)
|
||||
if group then
|
||||
for _, u in pairs(group:getUnits()) do
|
||||
if u and u:isExist() == true and u:inAir() == true then
|
||||
anyInAir = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not anyInAir then
|
||||
self:OnLastUnitLanded()
|
||||
end
|
||||
end
|
||||
|
||||
function AirGroup:OnGroupOnStation(groupName)
|
||||
if self._groupName == groupName then
|
||||
self:SetState("OnStation")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function AirGroup:Destroy()
|
||||
Spearhead.DcsUtil.DestroyGroup(self._groupName)
|
||||
end
|
||||
|
||||
if not Spearhead then Spearhead = {} end
|
||||
if not Spearhead.classes then Spearhead.classes = {} end
|
||||
if not Spearhead.classes.capClasses then Spearhead.classes.capClasses = {} end
|
||||
if not Spearhead.classes.capClasses.airGroups then Spearhead.classes.capClasses.airGroups = {} end
|
||||
Spearhead.classes.capClasses.airGroups.AirGroup = AirGroup
|
||||
|
||||
---@alias AirGroupState
|
||||
---| "UnSpawned"
|
||||
---| "ReadyOnTheRamp
|
||||
---| "InTransit"
|
||||
---| "OnStation"
|
||||
---| "RtbInTen"
|
||||
---| "Rtb"
|
||||
---| "Dead"
|
||||
---| "Repairing"
|
||||
---| "Rearming"
|
||||
|
||||
|
||||
---@alias AirGroupType
|
||||
---| "CAP"
|
||||
|
||||
---| "CAS"
|
||||
---| "SEAD"
|
||||
---| "INTERCEPT"
|
||||
---| ""
|
||||
@@ -0,0 +1,144 @@
|
||||
|
||||
---@class CapGroup : AirGroup
|
||||
---@field private _targetZoneIdPerStage table<string, string>
|
||||
---@field private _isBackup boolean
|
||||
---@field private _currentTargetZoneID string?
|
||||
local CapGroup = {}
|
||||
CapGroup.__index = CapGroup
|
||||
|
||||
|
||||
---@param groupName string
|
||||
---@param config CapConfig
|
||||
---@param logger Logger
|
||||
---@return CapGroup
|
||||
function CapGroup.New(groupName, config, logger)
|
||||
|
||||
setmetatable(CapGroup, Spearhead.classes.capClasses.airGroups.AirGroup)
|
||||
local self = setmetatable({}, CapGroup) --[[@as CapGroup]]
|
||||
Spearhead.classes.capClasses.airGroups.AirGroup.New(self, groupName, "CAP", config, logger)
|
||||
|
||||
self._targetZoneIdPerStage = {}
|
||||
|
||||
self:InitWithName(groupName)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
---@return boolean
|
||||
function CapGroup:IsBackup()
|
||||
return self._isBackup
|
||||
end
|
||||
|
||||
---@return string?
|
||||
function CapGroup:GetZoneIDWhenStageID(stageID)
|
||||
return self._targetZoneIdPerStage[stageID]
|
||||
end
|
||||
|
||||
---@return string?
|
||||
function CapGroup:GetCurrentTargetZoneID()
|
||||
return self._currentTargetZoneID
|
||||
end
|
||||
|
||||
---@param zone SpearheadTriggerZone
|
||||
---@param targetZoneID string
|
||||
---@param airbase Airbase
|
||||
function CapGroup:SendToZone(zone, targetZoneID, airbase)
|
||||
|
||||
self._logger:debug("Airgroup " .. self._groupName .. " called to zone: " .. zone.name)
|
||||
|
||||
self._currentTargetZoneID = targetZoneID
|
||||
local group = Group.getByName(self._groupName)
|
||||
|
||||
local isInAir = false
|
||||
if group then
|
||||
local units = group:getUnits()
|
||||
for _, unit in pairs(units) do
|
||||
if unit:inAir() == true then
|
||||
isInAir = true
|
||||
break
|
||||
end
|
||||
end
|
||||
else
|
||||
self._logger:debug("CapGroup:SendToZone - Group not found: " .. self._groupName)
|
||||
return
|
||||
end
|
||||
|
||||
if isInAir == true then
|
||||
local mission = Spearhead.classes.capClasses.taskings.CAP.getAsMission(self._groupName, airbase, zone, self._config)
|
||||
self:SetMission(mission)
|
||||
else
|
||||
local mission = Spearhead.classes.capClasses.taskings.CAP.getAsMissionFromAirbase(self._groupName, airbase, zone, self._config)
|
||||
if mission then
|
||||
self:SetMission(mission)
|
||||
else
|
||||
self._logger:warn("CapGroup:SendToZone - Mission could not be created for group: " .. self._groupName)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@private
|
||||
function CapGroup:InitWithName(groupName)
|
||||
local split_string = Spearhead.Util.split_string(groupName, "_")
|
||||
local partCount = Spearhead.Util.tableLength(split_string)
|
||||
if partCount >= 3 then
|
||||
|
||||
local configPart = split_string[2]
|
||||
local first = configPart:sub(1, 1)
|
||||
if first == "A" then
|
||||
self._isBackup = false
|
||||
configPart = string.sub(configPart, 2, #configPart)
|
||||
elseif first == "B" then
|
||||
configPart = string.sub(configPart, 2, #configPart)
|
||||
self._isBackup = true
|
||||
elseif first == "[" then
|
||||
self._isBackup = false
|
||||
else
|
||||
Spearhead.AddMissionEditorWarning("Could not parse the CAP config for group: " .. groupName)
|
||||
return
|
||||
end
|
||||
|
||||
local subsplit = Spearhead.Util.split_string(configPart, "|")
|
||||
if subsplit then
|
||||
for key, value in pairs(subsplit) do
|
||||
local keySplit = Spearhead.Util.split_string(value, "]")
|
||||
local targetZone = keySplit[2]
|
||||
local allActives = string.sub(keySplit[1], 2, #keySplit[1])
|
||||
local commaSeperated = Spearhead.Util.split_string(allActives, ",")
|
||||
for _, value in pairs(commaSeperated) do
|
||||
local dashSeperated = Spearhead.Util.split_string(value, "-")
|
||||
if Spearhead.Util.tableLength(dashSeperated) > 1 then
|
||||
local from = tonumber(dashSeperated[1])
|
||||
local till = tonumber(dashSeperated[2])
|
||||
|
||||
for i = from, till do
|
||||
if targetZone == "A" then
|
||||
self._targetZoneIdPerStage[tostring(i)] = tostring(i)
|
||||
else
|
||||
self._targetZoneIdPerStage[tostring(i)] = targetZone
|
||||
end
|
||||
end
|
||||
else
|
||||
if targetZone == "A" then
|
||||
self._targetZoneIdPerStage[tostring(dashSeperated[1])] = tostring(dashSeperated[1])
|
||||
else
|
||||
self._targetZoneIdPerStage[tostring(dashSeperated[1])] = targetZone
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
env.info("Capgroup parsed with table: " .. Spearhead.Util.toString(self._targetZoneIdPerStage))
|
||||
|
||||
else
|
||||
Spearhead.AddMissionEditorWarning("CAP Group with name: " .. groupName .. "should have at least 3 parts, but has " .. partCount)
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
if not Spearhead then Spearhead = {} end
|
||||
if not Spearhead.classes then Spearhead.classes = {} end
|
||||
if not Spearhead.classes.capClasses then Spearhead.classes.capClasses = {} end
|
||||
if not Spearhead.classes.capClasses.airGroups then Spearhead.classes.capClasses.airGroups = {} end
|
||||
Spearhead.classes.capClasses.airGroups.CapGroup = CapGroup
|
||||
Reference in New Issue
Block a user