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:
@@ -145,8 +145,8 @@
|
|||||||
|
|
||||||
<h3 id="cap-routes">CAP Routes</h3>
|
<h3 id="cap-routes">CAP Routes</h3>
|
||||||
<p>
|
<p>
|
||||||
<strong>Format:</strong> <span class="inline-lua"><span class="lua-variable">CAPROUTE_[Name]</span></span> <br />
|
<strong>Format:</strong> <span class="inline-lua"><span class="lua-variable">CAPROUTE_[routeID]_[Name]</span></span> <br />
|
||||||
<strong>Example:</strong> <span class="inline-lua"><span class="lua-variable">CAPROUTE_ALPHA</span></span>
|
<strong>Example:</strong> <span class="inline-lua"><span class="lua-variable">CAPROUTE_103_ALPHA</span></span>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
CAP routes define the patrol paths for CAP units. They are tied to specific stages and zones.
|
CAP routes define the patrol paths for CAP units. They are tied to specific stages and zones.
|
||||||
@@ -249,18 +249,16 @@
|
|||||||
|
|
||||||
<h3 id="cap-group-config">CAP Group Config</h3>
|
<h3 id="cap-group-config">CAP Group Config</h3>
|
||||||
<pre>
|
<pre>
|
||||||
<span class="lua-comment">1 at x:</span> <span class="lua-variable">[<activeStage>]<capStage></span>
|
<span class="lua-comment">1 at x:</span> <span class="lua-variable">[<activeStage>]<CapRouteID></span>
|
||||||
<span class="lua-comment">n and n at x:</span> <span class="lua-variable">[<activeStage>,<activeStage>]<capStage></span>
|
<span class="lua-comment">n and n at x:</span> <span class="lua-variable">[<activeStage>,<activeStage>]<CapRouteID></span>
|
||||||
<span class="lua-comment">n till n at x:</span> <span class="lua-variable">[<activeStage>-<activeStage>]<capStage></span>
|
<span class="lua-comment">n till n at x:</span> <span class="lua-variable">[<activeStage>-<activeStage>]<CapRouteID></span>
|
||||||
<span class="lua-comment">n till n and n at x:</span> <span class="lua-variable">[<activeStage>-<activeStage>,<activeStage>]<capStage></span>
|
<span class="lua-comment">n till n and n at x:</span> <span class="lua-variable">[<activeStage>-<activeStage>,<activeStage>]<CapRouteID></span>
|
||||||
<span class="lua-comment">n till n at Active:</span> <span class="lua-variable">[<activeStage>-<activeStage>]A</span>
|
|
||||||
|
|
||||||
<span class="lua-comment">Divider: |</span>
|
<span class="lua-comment">Divider: |</span>
|
||||||
|
|
||||||
<span class="lua-comment">Examples:</span>
|
<span class="lua-comment">Examples:</span>
|
||||||
<span class="lua-variable">CAP_A[1-4,6]7|[5,7]8_SomeName</span> <span class="lua-comment">=> Will fly CAP at stage 7 when stages 1 through 4 and 6 are active and will fly CAP at 8 when 5 and 7 are active.</span>
|
<span class="lua-variable">CAP_A[1-4,6]7|[5,7]8_SomeName</span> <span class="lua-comment">=> Will fly CAP at stage 7 when stages 1 through 4 and 6 are active and will fly CAP at 8 when 5 and 7 are active.</span>
|
||||||
<span class="lua-variable">CAP_A[2-5]5|[6]6_SomeName</span> <span class="lua-comment">=> Will fly CAP at stage 5 when stages 2 through 5 are active and will fly CAP at 6 when 6 is active.</span>
|
<span class="lua-variable">CAP_A[2-5]5|[6]6_SomeName</span> <span class="lua-comment">=> Will fly CAP at stage 5 when stages 2 through 5 are active and will fly CAP at CapRoute 6 when 6 is active.</span>
|
||||||
<span class="lua-variable">CAP_A[1-5]A|[6]7_SomeName</span> <span class="lua-comment">=> Will fly CAP at the ACTIVE stage if stages 1-5 are active. Then when 6 is active, it will fly in 7.</span>
|
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
<h3 id="active-and-backup-cap">Active and Backup CAP</h3>
|
<h3 id="active-and-backup-cap">Active and Backup CAP</h3>
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -1,74 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
---@class AirGroup
|
|
||||||
---@field groupName string
|
|
||||||
---@field groupState GroupState
|
|
||||||
local AirGroup = {}
|
|
||||||
|
|
||||||
---@alias GroupState
|
|
||||||
---| "UnSpawned"
|
|
||||||
---| "ReadOnTheRamp
|
|
||||||
---| "InTransit"
|
|
||||||
---| "OnStation"
|
|
||||||
---| "RtbInTen"
|
|
||||||
---| "Rtb"
|
|
||||||
---| "Dead"
|
|
||||||
---| "Rearming"
|
|
||||||
|
|
||||||
|
|
||||||
---@alias AirGroupType
|
|
||||||
---| "CAP"
|
|
||||||
|
|
||||||
---| "CAS"
|
|
||||||
---| "SEAD"
|
|
||||||
---| "INTERCEPT"
|
|
||||||
---| ""
|
|
||||||
|
|
||||||
|
|
||||||
---@class GroupNameData
|
|
||||||
---@field type AirGroupType
|
|
||||||
---@field isBackup boolean
|
|
||||||
---@field zonesConfig table<string, string>
|
|
||||||
|
|
||||||
local function parseGroupName(groupName)
|
|
||||||
|
|
||||||
local split_string = Spearhead.Util.split_string(groupName, "_")
|
|
||||||
local partCount = Spearhead.Util.tableLength(split_string)
|
|
||||||
|
|
||||||
if partCount >= 3 then
|
|
||||||
|
|
||||||
---@type boolean
|
|
||||||
local isBackup = false
|
|
||||||
|
|
||||||
do -- config
|
|
||||||
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
else
|
|
||||||
Spearhead.AddMissionEditorWarning("CAP Group with name: " .. groupName .. "should have at least 3 parts, but has " .. partCount)
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
---comment
|
|
||||||
---@generic T: AirGroup
|
|
||||||
---@param o T
|
|
||||||
---@param groupName string
|
|
||||||
---@return T
|
|
||||||
function AirGroup.New(o, groupName)
|
|
||||||
AirGroup.__index = AirGroup
|
|
||||||
local o = o or {}
|
|
||||||
local self = setmetatable(o, AirGroup)
|
|
||||||
|
|
||||||
self.groupName = groupName
|
|
||||||
self.groupState = "UnSpawned"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
+156
-144
@@ -1,22 +1,18 @@
|
|||||||
---@class CapBase : OnStageChangedListener
|
---@class CapBase : OnStageChangedListener
|
||||||
---@field groupNames Array<string>
|
---@field private airbaseName string
|
||||||
---@field database Database
|
---@field private logger table
|
||||||
---@field airbaseName string
|
---@field private database Database
|
||||||
---@field logger table
|
---@field private activeStage number
|
||||||
---@field activeStage number
|
---@field private capConfig table
|
||||||
---@field capConfig table
|
---@field private capGroupsByName table<string, CapGroup>
|
||||||
---@field activeCapStages number
|
|
||||||
---@field lastStatesByName table<string, string>
|
|
||||||
---@field groupsByName table<string, CapGroup>
|
|
||||||
---@field PrimaryGroups Array<CapGroup>
|
|
||||||
---@field BackupGroups Array<CapGroup>
|
|
||||||
---@field private runwayBombingTracker RunwayBombingTracker
|
---@field private runwayBombingTracker RunwayBombingTracker
|
||||||
---@field private runwayStrikeMissions table<string, RunwayStrikeMission>
|
---@field private runwayStrikeMissions table<string, RunwayStrikeMission>
|
||||||
local CapBase = {}
|
local CapBase = {}
|
||||||
|
|
||||||
|
|
||||||
local CheckReschedulingAsync = function(self, time)
|
local CheckStateContinuous = function(self, time)
|
||||||
self:CheckAndScheduleCAP()
|
self:CheckAndScheduleCAP()
|
||||||
|
return time + 15
|
||||||
end
|
end
|
||||||
|
|
||||||
---comment
|
---comment
|
||||||
@@ -28,181 +24,201 @@ end
|
|||||||
---@param runwayBombingTracker RunwayBombingTracker
|
---@param runwayBombingTracker RunwayBombingTracker
|
||||||
---@return CapBase
|
---@return CapBase
|
||||||
function CapBase.new(airbaseName, database, logger, capConfig, stageConfig, runwayBombingTracker)
|
function CapBase.new(airbaseName, database, logger, capConfig, stageConfig, runwayBombingTracker)
|
||||||
|
|
||||||
CapBase.__index = CapBase
|
CapBase.__index = CapBase
|
||||||
local self = setmetatable({}, { __index = CapBase }) --[[@as CapBase]]
|
local self = setmetatable({}, { __index = CapBase }) --[[@as CapBase]]
|
||||||
|
|
||||||
local baseData = database:getAirbaseDataForZone(airbaseName)
|
|
||||||
local groupNames = {}
|
|
||||||
if baseData then groupNames = baseData.CapGroups or {} end
|
|
||||||
|
|
||||||
self.groupNames = groupNames
|
|
||||||
self.database = database
|
|
||||||
self.runwayBombingTracker = runwayBombingTracker
|
self.runwayBombingTracker = runwayBombingTracker
|
||||||
self.runwayStrikeMissions = {}
|
self.runwayStrikeMissions = {}
|
||||||
|
|
||||||
self.airbaseName = airbaseName
|
self.airbaseName = airbaseName
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.activeStage = 0
|
self.activeStage = 0
|
||||||
self.capConfig = capConfig
|
self.capConfig = capConfig
|
||||||
self.activeCapStages = (stageConfig or {}).capActiveStages or 10
|
self.database = database
|
||||||
|
self.capGroupsByName = {}
|
||||||
|
|
||||||
self.lastStatesByName = {}
|
local baseData = database:getAirbaseDataForZone(airbaseName)
|
||||||
self.groupsByName = {}
|
if baseData and baseData.CapGroups then
|
||||||
self.PrimaryGroups = {}
|
for key, name in pairs(baseData.CapGroups) do
|
||||||
self.BackupGroups = {}
|
local capGroup = Spearhead.classes.capClasses.airGroups.CapGroup.New(name, capConfig, logger)
|
||||||
|
if capGroup then
|
||||||
for key, name in pairs(self.groupNames) do
|
self.capGroupsByName[name] = capGroup
|
||||||
local capGroup = Spearhead.classes.capClasses.CapGroup.new(name, airbaseName, logger, database, capConfig)
|
|
||||||
if capGroup then
|
|
||||||
self.groupsByName[name] = capGroup
|
|
||||||
|
|
||||||
if capGroup.isBackup ==true then
|
|
||||||
table.insert(self.BackupGroups, capGroup)
|
|
||||||
else
|
|
||||||
table.insert(self.PrimaryGroups, capGroup)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
capGroup:AddOnStateUpdatedListener(self)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
logger:info("Airbase with name '" .. airbaseName .. "' has a total of " .. Spearhead.Util.tableLength(self.groupsByName) .. " cap flights registered")
|
|
||||||
|
|
||||||
self:CreateRunwayStrikeMission()
|
logger:info("Airbase with name '" ..
|
||||||
|
airbaseName .. "' has a total of " .. Spearhead.Util.tableLength(self.capGroupsByName) .. " cap flights registered")
|
||||||
|
|
||||||
|
self:CreateRunwayStrikeMission(database)
|
||||||
|
|
||||||
Spearhead.Events.AddStageNumberChangedListener(self)
|
Spearhead.Events.AddStageNumberChangedListener(self)
|
||||||
|
|
||||||
return self
|
timer.scheduleFunction(CheckStateContinuous, self, timer.getTime() + 15)
|
||||||
|
|
||||||
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
---@private
|
---@private
|
||||||
function CapBase:CreateRunwayStrikeMission()
|
---@param database Database
|
||||||
|
function CapBase:CreateRunwayStrikeMission(database)
|
||||||
local airbase = Airbase.getByName(self.airbaseName)
|
local airbase = Airbase.getByName(self.airbaseName)
|
||||||
if not airbase then
|
if not airbase then
|
||||||
self.logger:debug("Could not find a airbase with name to create runway mission" .. self.airbaseName)
|
self.logger:debug("Could not find a airbase with name to create runway mission" .. self.airbaseName)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, runway in pairs(airbase:getRunways()) do
|
for _, runway in pairs(airbase:getRunways()) do
|
||||||
if runway then
|
if runway then
|
||||||
self.logger:debug("Runway " .. runway.Name .. " at airbase " .. self.airbaseName .. " with heading " .. runway.course .. " and length " .. runway.length .. " and width " .. runway.width)
|
self.logger:debug("Runway " ..
|
||||||
local mission = Spearhead.classes.stageClasses.missions.RunwayStrikeMission.new(runway, self.airbaseName, self.database, self.logger, self.runwayBombingTracker)
|
runway.Name ..
|
||||||
|
" at airbase " ..
|
||||||
|
self.airbaseName ..
|
||||||
|
" with heading " .. runway.course .. " and length " .. runway.length .. " and width " .. runway.width)
|
||||||
|
local mission = Spearhead.classes.stageClasses.missions.RunwayStrikeMission.new(runway, self.airbaseName,
|
||||||
|
database, self.logger, self.runwayBombingTracker)
|
||||||
self.runwayStrikeMissions[runway.Name] = mission
|
self.runwayStrikeMissions[runway.Name] = mission
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function CapBase:onGroupStateUpdated(capGroup)
|
function CapBase:SpawnIfApplicable()
|
||||||
--[[
|
self.logger:debug("Check spawns for airbase " .. self.airbaseName)
|
||||||
There is no update needed for INTRANSIT, ONSTATION or REARMING as the PREVIOUS state already was checked and nothing changes in the actual overal state.
|
for groupName, capGroup in pairs(self.capGroupsByName) do
|
||||||
]]--
|
local targetStage = capGroup:GetZoneIDWhenStageID(tostring(self.activeStage))
|
||||||
if capGroup.state == Spearhead.classes.capClasses.CapGroup.GroupState.INTRANSIT
|
|
||||||
or capGroup.state == Spearhead.classes.capClasses.CapGroup.GroupState.ONSTATION
|
if targetStage ~= nil and capGroup:GetState() == "UnSpawned" then
|
||||||
or capGroup.state == Spearhead.classes.capClasses.CapGroup.GroupState.REARMING
|
capGroup:Spawn()
|
||||||
then
|
end
|
||||||
return
|
|
||||||
end
|
end
|
||||||
timer.scheduleFunction(CheckReschedulingAsync, self, timer.getTime() + 1)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function CapBase:SpawnIfApplicable()
|
function CapBase:CheckAndScheduleCAP()
|
||||||
self.logger:debug("Check spawns for airbase " .. self.airbaseName )
|
self.logger:debug("Check taskings for airbase " .. self.airbaseName)
|
||||||
for groupName, capGroup in pairs(self.groupsByName) do
|
|
||||||
|
|
||||||
local targetStage = capGroup:GetTargetZone(self.activeStage)
|
|
||||||
|
|
||||||
if targetStage ~= nil and capGroup.state == Spearhead.classes.capClasses.CapGroup.GroupState.UNSPAWNED then
|
local countPerStage = {}
|
||||||
capGroup:SpawnOnTheRamp()
|
local requiredPerStage = {}
|
||||||
end
|
|
||||||
end
|
local airbase = Airbase.getByName(self.airbaseName)
|
||||||
|
if not airbase then
|
||||||
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
function CapBase:CheckAndScheduleCAP()
|
local activeStageID = tostring(self.activeStage)
|
||||||
|
|
||||||
self.logger:debug("Check taskings for airbase " .. self.airbaseName )
|
--Count back up groups that are active or reassign to the new zone if that's needed
|
||||||
|
for _, group in pairs(self.capGroupsByName) do
|
||||||
local countPerStage = {}
|
if group:IsBackup() == true then
|
||||||
local requiredPerStage = {}
|
local state = group:GetState()
|
||||||
|
if state == "InTransit" or state == "OnStation" or state == "RtbInTen" then
|
||||||
|
|
||||||
|
local supposedTargetZoneID = group:GetZoneIDWhenStageID(activeStageID)
|
||||||
|
local currentTargetZone = group:GetCurrentTargetZoneID()
|
||||||
|
|
||||||
--Count back up groups that are active or reassign to the new zone if that's needed
|
if supposedTargetZoneID == nil then
|
||||||
for _, backupGroup in pairs(self.BackupGroups) do
|
self.logger:debug("CapGroup " .. group:GetName() .. " has no target zone for stage " .. activeStageID)
|
||||||
if backupGroup.state == Spearhead.classes.capClasses.CapGroup.GroupState.INTRANSIT or backupGroup.state == Spearhead.classes.capClasses.CapGroup.GroupState.ONSTATION then
|
group:SendRTB(airbase)
|
||||||
local supposedTargetStage = backupGroup:GetTargetZone(self.activeStage)
|
|
||||||
if supposedTargetStage then
|
|
||||||
if supposedTargetStage ~= backupGroup.assignedStageNumber then
|
|
||||||
backupGroup:SendToStage(supposedTargetStage)
|
|
||||||
end
|
|
||||||
|
|
||||||
if countPerStage[supposedTargetStage] == nil then
|
|
||||||
countPerStage[supposedTargetStage] = 0
|
|
||||||
end
|
|
||||||
countPerStage[supposedTargetStage] = countPerStage[supposedTargetStage] + 1
|
|
||||||
else
|
else
|
||||||
backupGroup:SendRTBAndDespawn()
|
if supposedTargetZoneID and supposedTargetZoneID ~= currentTargetZone then
|
||||||
end
|
if state == "RtbInTen" then
|
||||||
elseif backupGroup.state == Spearhead.classes.capClasses.CapGroup.GroupState.RTBINTEN and backupGroup:GetTargetZone(self.activeStage) ~= backupGroup.assignedStageNumber then
|
self.logger:debug("CapGroup " .. group:GetName() .. " is RTB in 10 minutes, sending to RTB already")
|
||||||
backupGroup:SendRTB()
|
group:SendRTB(airbase)
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--Schedule or reassign primary units if applicable
|
|
||||||
for _, primaryGroup in pairs(self.PrimaryGroups) do
|
|
||||||
local supposedTargetStage = primaryGroup:GetTargetZone(self.activeStage)
|
|
||||||
if supposedTargetStage then
|
|
||||||
if requiredPerStage[supposedTargetStage] == nil then
|
|
||||||
requiredPerStage[supposedTargetStage] = 0
|
|
||||||
end
|
|
||||||
|
|
||||||
if countPerStage[supposedTargetStage] == nil
|
|
||||||
then
|
|
||||||
countPerStage[supposedTargetStage] = 0
|
|
||||||
end
|
|
||||||
|
|
||||||
requiredPerStage[supposedTargetStage] = requiredPerStage[supposedTargetStage] + 1
|
|
||||||
|
|
||||||
if primaryGroup.state == Spearhead.classes.capClasses.CapGroup.GroupState.READYONRAMP then
|
|
||||||
if countPerStage[supposedTargetStage] < requiredPerStage[supposedTargetStage] then
|
|
||||||
primaryGroup:SendToStage(supposedTargetStage)
|
|
||||||
countPerStage[supposedTargetStage] = countPerStage[supposedTargetStage] + 1
|
|
||||||
end
|
|
||||||
elseif primaryGroup.state == Spearhead.classes.capClasses.CapGroup.GroupState.INTRANSIT or primaryGroup.state == Spearhead.classes.capClasses.CapGroup.GroupState.ONSTATION then
|
|
||||||
if supposedTargetStage ~= primaryGroup.assignedStageNumber then
|
|
||||||
if countPerStage[supposedTargetStage] < requiredPerStage[supposedTargetStage] then
|
|
||||||
primaryGroup:SendToStage(supposedTargetStage)
|
|
||||||
else
|
else
|
||||||
countPerStage[supposedTargetStage] = countPerStage[supposedTargetStage] + 1
|
local triggerZone = self.database:GetCapZoneForZoneID(supposedTargetZoneID)
|
||||||
primaryGroup:SendRTB()
|
if triggerZone then
|
||||||
|
group:SendToZone(triggerZone, supposedTargetZoneID, airbase)
|
||||||
|
else
|
||||||
|
self.logger:debug("CapGroup " .. group:GetName() .. " has no trigger zone for stage " .. activeStageID)
|
||||||
|
group:SendRTB(airbase)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
countPerStage[supposedTargetStage] = countPerStage[supposedTargetStage] + 1
|
|
||||||
elseif primaryGroup.state == Spearhead.classes.capClasses.CapGroup.GroupState.RTBINTEN and primaryGroup:GetTargetZone(self.activeStage) ~= primaryGroup.assignedStageNumber then
|
if countPerStage[supposedTargetZoneID] == nil then
|
||||||
primaryGroup:SendRTB()
|
countPerStage[supposedTargetZoneID] = 0
|
||||||
end
|
end
|
||||||
else
|
|
||||||
primaryGroup:SendRTBAndDespawn()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, backupGroup in pairs(self.BackupGroups) do
|
if supposedTargetZoneID == group:GetCurrentTargetZoneID() and (state == "OnStation" or state =="InTransit") then
|
||||||
if backupGroup.state == Spearhead.classes.capClasses.CapGroup.GroupState.READYONRAMP then
|
countPerStage[supposedTargetZoneID] = countPerStage[supposedTargetZoneID] + 1
|
||||||
local supposedTargetStage = backupGroup:GetTargetZone(self.activeStage)
|
|
||||||
if supposedTargetStage then
|
|
||||||
if countPerStage[supposedTargetStage] == nil then
|
|
||||||
countPerStage[supposedTargetStage] = 0
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if countPerStage[supposedTargetStage] < requiredPerStage[supposedTargetStage] then
|
|
||||||
backupGroup:SendToStage(supposedTargetStage)
|
|
||||||
countPerStage[supposedTargetStage] = countPerStage[supposedTargetStage] + 1
|
|
||||||
end
|
|
||||||
else
|
|
||||||
backupGroup:SendRTBAndDespawn()
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--Schedule or reassign primary units if applicable
|
||||||
|
for _, group in pairs(self.capGroupsByName) do
|
||||||
|
if group:IsBackup() == false then
|
||||||
|
local state = group:GetState()
|
||||||
|
local supposedZone = group:GetZoneIDWhenStageID(activeStageID)
|
||||||
|
if supposedZone then
|
||||||
|
if requiredPerStage[supposedZone] == nil then
|
||||||
|
requiredPerStage[supposedZone] = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
if countPerStage[supposedZone] == nil then
|
||||||
|
countPerStage[supposedZone] = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
requiredPerStage[supposedZone] = requiredPerStage[supposedZone] + 1
|
||||||
|
|
||||||
|
if state == "ReadyOnTheRamp" then
|
||||||
|
if countPerStage[supposedZone] < requiredPerStage[supposedZone] then
|
||||||
|
local triggerZone = self.database:GetCapZoneForZoneID(supposedZone)
|
||||||
|
if triggerZone then
|
||||||
|
group:SendToZone(triggerZone, supposedZone, airbase)
|
||||||
|
end
|
||||||
|
|
||||||
|
countPerStage[supposedZone] = countPerStage[supposedZone] + 1
|
||||||
|
end
|
||||||
|
elseif state == "InTransit" or state == "OnStation" then
|
||||||
|
|
||||||
|
if supposedZone ~= group:GetCurrentTargetZoneID() then
|
||||||
|
if countPerStage[supposedZone] < requiredPerStage[supposedZone] then
|
||||||
|
local triggerZone = self.database:GetCapZoneForZoneID(supposedZone)
|
||||||
|
if triggerZone then
|
||||||
|
group:SendToZone(triggerZone, supposedZone, airbase)
|
||||||
|
else
|
||||||
|
group:SendRTB(airbase)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
countPerStage[supposedZone] = countPerStage[supposedZone] + 1
|
||||||
|
elseif state == "RtbInTen" and supposedZone ~= group:GetCurrentTargetZoneID() then
|
||||||
|
group:SendRTB(airbase)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if state == "InTransit" or state == "OnStation" or state == "RtbInTen" then
|
||||||
|
-- If the group is in transit or on station but has no target zone, send it back to base
|
||||||
|
group:SendRTB(airbase)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, group in pairs(self.capGroupsByName) do
|
||||||
|
if group:IsBackup() == true then
|
||||||
|
if group:GetState() == "ReadyOnTheRamp" then
|
||||||
|
local supposedZone = group:GetZoneIDWhenStageID(activeStageID)
|
||||||
|
if supposedZone then
|
||||||
|
if countPerStage[supposedZone] == nil then
|
||||||
|
countPerStage[supposedZone] = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
if countPerStage[supposedZone] < requiredPerStage[supposedZone] then
|
||||||
|
local triggerZone = self.database:GetCapZoneForZoneID(supposedZone)
|
||||||
|
if triggerZone then
|
||||||
|
group:SendToZone(triggerZone, supposedZone, airbase)
|
||||||
|
end
|
||||||
|
|
||||||
|
countPerStage[supposedZone] = countPerStage[supposedZone] + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function CapBase:OnStageNumberChanged(number)
|
function CapBase:OnStageNumberChanged(number)
|
||||||
self.activeStage = number
|
self.activeStage = number
|
||||||
|
|
||||||
@@ -211,25 +227,21 @@ function CapBase:OnStageNumberChanged(number)
|
|||||||
mission:SpawnActive()
|
mission:SpawnActive()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
self:SpawnIfApplicable()
|
self:SpawnIfApplicable()
|
||||||
timer.scheduleFunction(CheckReschedulingAsync, self, timer.getTime() + 5)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
---@param stageNumber number
|
---@param stageNumber number
|
||||||
---@return boolean
|
---@return boolean
|
||||||
function CapBase:IsBaseActiveWhenStageIsActive(stageNumber)
|
function CapBase:IsBaseActiveWhenStageIsActive(stageNumber)
|
||||||
for _, group in pairs(self.PrimaryGroups) do
|
for _, group in pairs(self.capGroupsByName) do
|
||||||
local target = group:GetTargetZone(stageNumber)
|
local target = group:GetZoneIDWhenStageID(tostring(stageNumber))
|
||||||
if target ~= nil then
|
if group:IsBackup() == false and target ~= nil then
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
if not Spearhead then Spearhead = {} end
|
if not Spearhead then Spearhead = {} end
|
||||||
if not Spearhead.classes then Spearhead.classes = {} end
|
if not Spearhead.classes then Spearhead.classes = {} end
|
||||||
if not Spearhead.classes.capClasses then Spearhead.classes.capClasses = {} end
|
if not Spearhead.classes.capClasses then Spearhead.classes.capClasses = {} end
|
||||||
|
|||||||
@@ -1,429 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
local CapHelper = {}
|
|
||||||
do
|
|
||||||
---comment
|
|
||||||
---@param groupName string
|
|
||||||
---@return table?
|
|
||||||
CapHelper.ParseGroupName = function(groupName)
|
|
||||||
local split_string = Spearhead.Util.split_string(groupName, "_")
|
|
||||||
local partCount = Spearhead.Util.tableLength(split_string)
|
|
||||||
if partCount >= 3 then
|
|
||||||
local result = {}
|
|
||||||
result.zonesConfig = {}
|
|
||||||
|
|
||||||
-- CAP_[1-5]5|[6]6|[7]7_Sukhoi
|
|
||||||
-- CAP_[1-5,7]A|[6]7_Sukhoi
|
|
||||||
|
|
||||||
local configPart = split_string[2]
|
|
||||||
local first = configPart:sub(1, 1)
|
|
||||||
if first == "A" then
|
|
||||||
result.isBackup = false
|
|
||||||
configPart = string.sub(configPart, 2, #configPart)
|
|
||||||
elseif first == "B" then
|
|
||||||
configPart = string.sub(configPart, 2, #configPart)
|
|
||||||
result.isBackup = true
|
|
||||||
elseif first == "[" then
|
|
||||||
result.isBackup = false
|
|
||||||
else
|
|
||||||
Spearhead.AddMissionEditorWarning("Could not parse the CAP config for group: " .. groupName)
|
|
||||||
return nil
|
|
||||||
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
|
|
||||||
result.zonesConfig[tostring(i)] = tostring(i)
|
|
||||||
else
|
|
||||||
result.zonesConfig[tostring(i)] = tostring(targetZone)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if targetZone == "A" then
|
|
||||||
result.zonesConfig[tostring(dashSeperated[1])] = tostring(dashSeperated[1])
|
|
||||||
else
|
|
||||||
result.zonesConfig[tostring(dashSeperated[1])] = tostring(targetZone)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return result
|
|
||||||
else
|
|
||||||
Spearhead.AddMissionEditorWarning("CAP Group with name: " .. groupName .. "should have at least 3 parts, but has " .. partCount)
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---comment
|
|
||||||
---@param input table { groupName, task, logger }
|
|
||||||
---@param time number
|
|
||||||
---@return nil
|
|
||||||
local function setTaskAsync(input, time)
|
|
||||||
local task = input.task
|
|
||||||
local groupName = input.groupName
|
|
||||||
local group = Group.getByName(groupName)
|
|
||||||
|
|
||||||
if task and group then
|
|
||||||
group:getController():setTask(task)
|
|
||||||
if input.logger ~= nil then
|
|
||||||
input.logger:debug("task set succesfully to group " .. groupName)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
---@class OnUpdateListener
|
|
||||||
---@field onGroupStateUpdated fun(self: OnUpdateListener, capGroup: CapGroup)
|
|
||||||
|
|
||||||
---@class CapGroup : OnUnitLostListener
|
|
||||||
---@field isBackup boolean
|
|
||||||
---@field groupName string
|
|
||||||
---@field assignedStageNumber string
|
|
||||||
---@field private airbaseName string
|
|
||||||
---@field private logger Logger
|
|
||||||
---@field private database Database
|
|
||||||
---@field private capConfig table
|
|
||||||
---@field private capZonesConfig table<string, string>
|
|
||||||
---@field private aliveUnits table<string, boolean>
|
|
||||||
---@field private onStatusUpdatedListener Array<OnUpdateListener>
|
|
||||||
local CapGroup = {}
|
|
||||||
|
|
||||||
CapGroup.GroupState = {
|
|
||||||
UNSPAWNED = 0,
|
|
||||||
READYONRAMP = 1,
|
|
||||||
INTRANSIT = 2,
|
|
||||||
ONSTATION = 3,
|
|
||||||
RTBINTEN = 4,
|
|
||||||
RTB = 5,
|
|
||||||
DEAD = 6,
|
|
||||||
REARMING = 7
|
|
||||||
}
|
|
||||||
|
|
||||||
local function SetReadyOnRampAsync(self, time)
|
|
||||||
self:SetState(CapGroup.GroupState.READYONRAMP)
|
|
||||||
end
|
|
||||||
|
|
||||||
local RESPAWN_AFTER_TOUCHDOWN_SECONDS = 180
|
|
||||||
|
|
||||||
---comment
|
|
||||||
---@param groupName string
|
|
||||||
---@param airbaseName string
|
|
||||||
---@param logger Logger logger dependency injection
|
|
||||||
---@param database Database database dependency injection
|
|
||||||
---@param capConfig table config dependency injection
|
|
||||||
---@return CapGroup? self
|
|
||||||
function CapGroup.new(groupName, airbaseName, logger, database, capConfig)
|
|
||||||
|
|
||||||
CapGroup.__index = CapGroup
|
|
||||||
local self = setmetatable({}, CapGroup)
|
|
||||||
|
|
||||||
Spearhead.DcsUtil.DestroyGroup(groupName)
|
|
||||||
|
|
||||||
-- initials
|
|
||||||
self.groupName = groupName
|
|
||||||
self.airbaseName = airbaseName
|
|
||||||
|
|
||||||
local airbase = Airbase.getByName(airbaseName)
|
|
||||||
if airbase == nil then
|
|
||||||
logger:error("Airbase with name " .. airbaseName .. " does not exist")
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
self.airbaseId = airbase:getID()
|
|
||||||
self.logger = logger
|
|
||||||
self.database = database
|
|
||||||
|
|
||||||
local parsed = CapHelper.ParseGroupName(groupName)
|
|
||||||
if parsed == nil then return nil end
|
|
||||||
self.capZonesConfig = parsed.zonesConfig
|
|
||||||
self.isBackup = parsed.isBackup
|
|
||||||
|
|
||||||
--vars
|
|
||||||
self.assignedStageNumber = nil
|
|
||||||
|
|
||||||
self.state = CapGroup.GroupState.UNSPAWNED
|
|
||||||
self.aliveUnits = {}
|
|
||||||
self.landedUnits = {}
|
|
||||||
self.unitCount = 0
|
|
||||||
self.onStationSince = 0
|
|
||||||
self.currentCapTaskingDuration = 0
|
|
||||||
self.markedForDespawn = false
|
|
||||||
|
|
||||||
self.onStatusUpdatedListener = {}
|
|
||||||
|
|
||||||
--config
|
|
||||||
self.capConfig = capConfig
|
|
||||||
|
|
||||||
Spearhead.Events.addOnGroupRTBListener(self.groupName, self)
|
|
||||||
Spearhead.Events.addOnGroupRTBInTenListener(self.groupName, self)
|
|
||||||
Spearhead.Events.addOnGroupOnStationListener(self.groupName, self)
|
|
||||||
local units = Group.getByName(groupName):getUnits()
|
|
||||||
for key, unit in pairs(units) do
|
|
||||||
Spearhead.Events.addOnUnitLandEventListener(unit:getName(), self)
|
|
||||||
Spearhead.Events.addOnUnitLostEventListener(unit:getName(), self)
|
|
||||||
end
|
|
||||||
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param currentActive number
|
|
||||||
---@return string
|
|
||||||
function CapGroup:GetTargetZone(currentActive)
|
|
||||||
return self.capZonesConfig[tostring(currentActive)]
|
|
||||||
end
|
|
||||||
|
|
||||||
function CapGroup:SetState(state)
|
|
||||||
self.state = state
|
|
||||||
self:PublishUnitUpdatedEvent()
|
|
||||||
end
|
|
||||||
|
|
||||||
function CapGroup:StartRearm()
|
|
||||||
self:SpawnOnTheRamp()
|
|
||||||
self:SetState(CapGroup.GroupState.REARMING)
|
|
||||||
timer.scheduleFunction(SetReadyOnRampAsync, self, timer.getTime() + self.capConfig:getRearmDelay() - RESPAWN_AFTER_TOUCHDOWN_SECONDS)
|
|
||||||
end
|
|
||||||
|
|
||||||
function CapGroup:SpawnOnTheRamp()
|
|
||||||
self.markedForDespawn = false
|
|
||||||
self.logger:debug("Spawning group " .. self.groupName)
|
|
||||||
self.aliveUnits = {}
|
|
||||||
self.landedUnits = {}
|
|
||||||
self.onStationSince = 0
|
|
||||||
|
|
||||||
local group = Spearhead.DcsUtil.SpawnGroupTemplate(self.groupName, nil, nil, true)
|
|
||||||
if group then
|
|
||||||
self.unitCount = group:getInitialSize()
|
|
||||||
|
|
||||||
if self.state == CapGroup.GroupState.UNSPAWNED then
|
|
||||||
self:SetState(CapGroup.GroupState.READYONRAMP)
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, unit in pairs(group:getUnits()) do
|
|
||||||
local name = unit:getName()
|
|
||||||
self.aliveUnits[name] = true
|
|
||||||
self.landedUnits[name] = false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function CapGroup:Despawn()
|
|
||||||
self.logger:debug("Despawning group " .. self.groupName)
|
|
||||||
Spearhead.DcsUtil.DestroyGroup(self.groupName)
|
|
||||||
self:SetState(CapGroup.GroupState.UNSPAWNED)
|
|
||||||
end
|
|
||||||
|
|
||||||
function CapGroup:SendRTB()
|
|
||||||
local group = Group.getByName(self.groupName)
|
|
||||||
if group and group:isExist() then
|
|
||||||
local speed = math.random(self.capConfig:getMinSpeed(), self.capConfig:getMaxSpeed())
|
|
||||||
local rtbTask, errormessage = Spearhead.RouteUtil.CreateRTBMission(self.groupName, self.airbaseId, speed)
|
|
||||||
if rtbTask then
|
|
||||||
timer.scheduleFunction(setTaskAsync, { task = rtbTask, groupName = self.groupName, logger = self.logger }, timer.getTime() + 3)
|
|
||||||
else
|
|
||||||
self.logger:error("No RTB task could be created for group: " .. self.groupName .. " due to " .. errormessage)
|
|
||||||
if self.markedForDespawn == true then
|
|
||||||
self:Despawn()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function CapGroup:SendRTBAndDespawn()
|
|
||||||
self.markedForDespawn = true
|
|
||||||
self:SendRTB()
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param stageZoneNumber string
|
|
||||||
function CapGroup:SendToStage(stageZoneNumber)
|
|
||||||
if self.state == CapGroup.GroupState.DEAD or self.state == CapGroup.GroupState.RTB then
|
|
||||||
return --Can't task a unit that's dead or RTB
|
|
||||||
end
|
|
||||||
|
|
||||||
self.assignedStageNumber = stageZoneNumber
|
|
||||||
local group = Group.getByName(self.groupName)
|
|
||||||
if group and group:isExist() then
|
|
||||||
self.logger:debug("Sending group out " .. self.groupName)
|
|
||||||
local controller = group:getController()
|
|
||||||
local capPoints = self.database:getCapRouteInZone(stageZoneNumber, self.airbaseName)
|
|
||||||
|
|
||||||
if not capPoints then
|
|
||||||
self.logger:error("No CAP route found for group " .. self.groupName .. " in zone " .. stageZoneNumber)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local altitude = math.random(self.capConfig:getMinAlt(), self.capConfig:getMaxAlt())
|
|
||||||
local speed = math.random(self.capConfig:getMinSpeed(), self.capConfig:getMaxSpeed())
|
|
||||||
local attackHelos = false
|
|
||||||
local deviationDistance = self.capConfig:getMaxDeviationRange()
|
|
||||||
local capTask
|
|
||||||
if self.state == CapGroup.GroupState.ONRAMP or self.onStationSince == 0 then
|
|
||||||
controller:setCommand({
|
|
||||||
id = 'Start',
|
|
||||||
params = {}
|
|
||||||
})
|
|
||||||
local duration = math.random(self.capConfig:getMinDurationOnStation(), self.capConfig:getmaxDurationOnStation())
|
|
||||||
self.currentCapTaskingDuration = duration
|
|
||||||
|
|
||||||
|
|
||||||
capTask = Spearhead.RouteUtil.createCapMission(self.groupName, self.airbaseId, capPoints.point1, capPoints.point2, altitude, speed, duration, attackHelos, deviationDistance)
|
|
||||||
else
|
|
||||||
local duration = self.currentCapTaskingDuration - (timer.getTime() - self.onStationSince)
|
|
||||||
capTask = Spearhead.RouteUtil.createCapMission(self.groupName, self.airbaseId, capPoints.point1, capPoints.point2, altitude, speed, duration, attackHelos, deviationDistance)
|
|
||||||
end
|
|
||||||
|
|
||||||
if capTask then
|
|
||||||
timer.scheduleFunction(setTaskAsync,
|
|
||||||
{ task = capTask, groupName = self.groupName, logger = self.logger }, timer.getTime() + 3)
|
|
||||||
end
|
|
||||||
self:SetState(CapGroup.GroupState.INTRANSIT)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param airdomeId any
|
|
||||||
function CapGroup:SendToAirbase(airdomeId)
|
|
||||||
self.airbaseId = airdomeId
|
|
||||||
local speed = math.random(self.capConfig:getMinSpeed(), self.capConfig:getMaxSpeed())
|
|
||||||
local rtbTask = Spearhead.RouteUtil.CreateRTBMission(self.groupName, airdomeId, speed)
|
|
||||||
local group = Group.getByName(self.groupName)
|
|
||||||
|
|
||||||
if not group then return end
|
|
||||||
local controller = group:getController()
|
|
||||||
controller:setCommand({
|
|
||||||
id = 'Start',
|
|
||||||
params = {}
|
|
||||||
})
|
|
||||||
timer.scheduleFunction(setTaskAsync, { task = rtbTask, groupName = self.groupName, logger = self.logger },
|
|
||||||
timer.getTime() + 5)
|
|
||||||
end
|
|
||||||
|
|
||||||
function CapGroup:OnGroupRTB(groupName)
|
|
||||||
if groupName == self.groupName then
|
|
||||||
self.logger:debug("Setting group " ..
|
|
||||||
groupName ..
|
|
||||||
" to state RTB after a total of " ..
|
|
||||||
timer.getTime() - self.onStationSince .. "s of the " .. self.currentCapTaskingDuration .. "s")
|
|
||||||
self:SetState(CapGroup.GroupState.RTB)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function CapGroup:OnGroupRTBInTen(groupName)
|
|
||||||
if groupName == self.groupName then
|
|
||||||
self:SetState(CapGroup.GroupState.RTBINTEN)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function CapGroup:OnGroupOnStation(groupName)
|
|
||||||
if groupName == self.groupName then
|
|
||||||
self.onStationSince = timer.getTime()
|
|
||||||
self.logger:debug("Setting group " .. groupName .. " to state Onstation")
|
|
||||||
self:SetState(CapGroup.GroupState.ONSTATION)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
---@param proActive boolean Will check all units in group for aliveness
|
|
||||||
function CapGroup:UpdateState(proActive)
|
|
||||||
local landed = false
|
|
||||||
local landedCount = 0
|
|
||||||
for name, landedBool in pairs(self.landedUnits) do
|
|
||||||
if landedBool == true then
|
|
||||||
landedCount = landedCount + 1
|
|
||||||
landed = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local deadCount = 0
|
|
||||||
for name, isAlive in pairs(self.aliveUnits) do
|
|
||||||
if isAlive == false then
|
|
||||||
deadCount = deadCount + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function DelayedStartRearm(input, time)
|
|
||||||
local capGroup = input.self
|
|
||||||
capGroup:StartRearm()
|
|
||||||
end
|
|
||||||
|
|
||||||
if landedCount + deadCount == self.unitCount then
|
|
||||||
if landed then
|
|
||||||
if self.markedForDespawn == true then
|
|
||||||
self:Despawn()
|
|
||||||
else
|
|
||||||
timer.scheduleFunction(DelayedStartRearm, { self = self }, timer.getTime() + RESPAWN_AFTER_TOUCHDOWN_SECONDS)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if self.markedForDespawn == true then
|
|
||||||
self:Despawn()
|
|
||||||
else
|
|
||||||
local delay = self.capConfig:getDeathDelay() - self.capConfig:getRearmDelay() + RESPAWN_AFTER_TOUCHDOWN_SECONDS
|
|
||||||
timer.scheduleFunction(DelayedStartRearm, { self = self }, timer.getTime() + delay)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
---@param listener table object with function OnGroupStateUpdated(capGroupTable)
|
|
||||||
function CapGroup:AddOnStateUpdatedListener(listener)
|
|
||||||
if type(listener) ~= "table" then
|
|
||||||
self.logger:error("Listener not of type table for AddOnStateUpdatedListener")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if listener.onGroupStateUpdated == nil then
|
|
||||||
self.logger:error("Listener does not implement onGroupStateUpdated")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
table.insert(self.onStatusUpdatedListener, listener)
|
|
||||||
end
|
|
||||||
|
|
||||||
function CapGroup:PublishUnitUpdatedEvent()
|
|
||||||
for _, callable in pairs(self.onStatusUpdatedListener) do
|
|
||||||
local _, error = pcall(function()
|
|
||||||
callable:onGroupStateUpdated(self)
|
|
||||||
end)
|
|
||||||
if error then
|
|
||||||
self.logger:error(error)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function CapGroup:OnUnitLanded(initiatorUnit, airbase)
|
|
||||||
if airbase then
|
|
||||||
self.airbaseName = airbase:getName()
|
|
||||||
end
|
|
||||||
local name = initiatorUnit:getName()
|
|
||||||
self.logger:debug("Received unit land event for unit " .. name .. " of group " .. self.groupName)
|
|
||||||
|
|
||||||
self.landedUnits[name] = true
|
|
||||||
self:UpdateState(false)
|
|
||||||
end
|
|
||||||
|
|
||||||
function CapGroup:OnUnitLost(initiatorUnit)
|
|
||||||
self.logger:debug("Received unit lost event for group " .. self.groupName)
|
|
||||||
if initiatorUnit then
|
|
||||||
self.aliveUnits[initiatorUnit:getName()] = false
|
|
||||||
end
|
|
||||||
self:UpdateState(false)
|
|
||||||
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
|
|
||||||
Spearhead.classes.capClasses.CapGroup = CapGroup
|
|
||||||
@@ -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
|
||||||
@@ -21,16 +21,12 @@ end
|
|||||||
---@param weapon Weapon
|
---@param weapon Weapon
|
||||||
function RunwayBombingTracker:OnWeaponFired(unit, weapon, target)
|
function RunwayBombingTracker:OnWeaponFired(unit, weapon, target)
|
||||||
|
|
||||||
if weapon == nil then
|
if weapon == nil then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
self._logger:debug("Tracking weapon for runway impact onWeaponFired")
|
|
||||||
|
|
||||||
local desc = weapon:getDesc()
|
local desc = weapon:getDesc()
|
||||||
|
|
||||||
local isTrackable = desc.category == Weapon.Category.BOMB or (desc.category == Weapon.Category.MISSILE and desc.missileCategory == Weapon.MissileCategory.CRUISE)
|
local isTrackable = desc.category == Weapon.Category.BOMB or (desc.category == Weapon.Category.MISSILE and desc.missileCategory == Weapon.MissileCategory.CRUISE)
|
||||||
|
|
||||||
if isTrackable == true then
|
if isTrackable == true then
|
||||||
|
|
||||||
---@type WeaponTrackingArgs
|
---@type WeaponTrackingArgs
|
||||||
|
|||||||
@@ -0,0 +1,327 @@
|
|||||||
|
---@class CAPTasking
|
||||||
|
local CAP = {}
|
||||||
|
|
||||||
|
---@param attackHelos boolean
|
||||||
|
---@return table
|
||||||
|
local function GetCAPTargetTypes(attackHelos)
|
||||||
|
local targetTypes = {
|
||||||
|
[1] = "Planes",
|
||||||
|
}
|
||||||
|
|
||||||
|
if attackHelos then
|
||||||
|
targetTypes[2] = "Helicopters"
|
||||||
|
end
|
||||||
|
|
||||||
|
return targetTypes
|
||||||
|
end
|
||||||
|
|
||||||
|
---@class CapTaskingOptions
|
||||||
|
---@field furthest Vec2
|
||||||
|
---@field closest Vec2
|
||||||
|
---@field legLength number
|
||||||
|
---@field width number
|
||||||
|
---@field hotLegDir number
|
||||||
|
|
||||||
|
|
||||||
|
---@param capZone SpearheadTriggerZone
|
||||||
|
---@param airBase Airbase
|
||||||
|
---@return CapTaskingOptions
|
||||||
|
local function GetCAPPointFromTriggerZone(airBase, capZone)
|
||||||
|
local furthestA = nil
|
||||||
|
local furthestB = nil
|
||||||
|
|
||||||
|
local furthestFromBase = nil
|
||||||
|
local furthestFromBaseDistance = 0
|
||||||
|
|
||||||
|
local furthestDistance = 0
|
||||||
|
|
||||||
|
for indexA, pointA in ipairs(capZone.verts) do
|
||||||
|
for indexB, pointB in ipairs(capZone.verts) do
|
||||||
|
if pointA ~= pointB then
|
||||||
|
local distance = Spearhead.Util.VectorDistance2d(pointA, pointB)
|
||||||
|
if distance > furthestDistance then
|
||||||
|
furthestDistance = distance
|
||||||
|
furthestA = indexA
|
||||||
|
furthestB = indexB
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local baseVec3 = airBase:getPoint()
|
||||||
|
|
||||||
|
---@type Vec2
|
||||||
|
local baseVec2 = { x = baseVec3.x, y = baseVec3.z }
|
||||||
|
|
||||||
|
local pointA = capZone.verts[furthestA]
|
||||||
|
local pointB = capZone.verts[furthestB]
|
||||||
|
local furthest = pointA
|
||||||
|
local closest = pointB
|
||||||
|
|
||||||
|
local heading = Spearhead.Util.vectorHeadingFromTo(pointA, pointB)
|
||||||
|
|
||||||
|
if Spearhead.Util.VectorDistance2d(baseVec2, pointB) > Spearhead.Util.VectorDistance2d(baseVec2, pointA) then
|
||||||
|
furthest = pointB
|
||||||
|
closest = pointA
|
||||||
|
heading = Spearhead.Util.vectorHeadingFromTo(pointB, pointA)
|
||||||
|
end
|
||||||
|
|
||||||
|
local distance = furthestDistance
|
||||||
|
if distance > 15000 then
|
||||||
|
distance = distance - 10000
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
width = 10000,
|
||||||
|
furthest = furthest,
|
||||||
|
closest = closest,
|
||||||
|
legLength = distance,
|
||||||
|
hotLegDir = math.rad(heading),
|
||||||
|
orbitOriginPoint = closest
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param airbase Airbase
|
||||||
|
---@param capZone SpearheadTriggerZone
|
||||||
|
---@param capConfig CapConfig
|
||||||
|
local GetOutboundTask = function(airbase, capZone, capConfig)
|
||||||
|
local airbaseVec3 = airbase:getPoint()
|
||||||
|
local airbaseVec2 = { x = airbaseVec3.x, y = airbaseVec3.z }
|
||||||
|
local heading = Spearhead.Util.vectorHeadingFromTo(airbaseVec2, capZone.location)
|
||||||
|
local point = Spearhead.Util.vectorMove(airbaseVec2, heading, 18520)
|
||||||
|
|
||||||
|
return {
|
||||||
|
alt = 2000,
|
||||||
|
action = "Fly Over Point",
|
||||||
|
type = "Turning Point",
|
||||||
|
alt_type = "BARO",
|
||||||
|
speed = capConfig:getMinSpeed(),
|
||||||
|
ETA = 0,
|
||||||
|
ETA_locked = false,
|
||||||
|
x = point.x,
|
||||||
|
y = point.y,
|
||||||
|
speed_locked = false,
|
||||||
|
formation_template = "",
|
||||||
|
task = {
|
||||||
|
id = "ComboTask",
|
||||||
|
params = {
|
||||||
|
tasks = {
|
||||||
|
[1] = {
|
||||||
|
id = 'EngageTargets',
|
||||||
|
params = {
|
||||||
|
maxDist = capConfig:getMaxDeviationRange() + 10 * 1852,
|
||||||
|
maxDistEnabled = capConfig:getMaxDeviationRange() > 0, -- required to check maxDist
|
||||||
|
targetTypes = GetCAPTargetTypes(false),
|
||||||
|
priority = 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param groupName string
|
||||||
|
---@param airbase Airbase
|
||||||
|
---@param capZone SpearheadTriggerZone
|
||||||
|
---@param capConfig CapConfig
|
||||||
|
function CAP.getAsMissionFromAirbase(groupName, airbase, capZone, capConfig)
|
||||||
|
local points = {
|
||||||
|
[1] = GetOutboundTask(airbase, capZone, capConfig),
|
||||||
|
[2] = GetOutboundTask(airbase, capZone, capConfig),
|
||||||
|
[3] = CAP.getAsTasking(groupName, airbase, capZone, capConfig),
|
||||||
|
[4] = Spearhead.classes.capClasses.taskings.RTB.getApproachPoint(airbase, capZone.location, capConfig),
|
||||||
|
[5] = Spearhead.classes.capClasses.taskings.RTB.getInitialPoint(airbase),
|
||||||
|
[6] = Spearhead.classes.capClasses.taskings.RTB.getLandingPoint(airbase)
|
||||||
|
}
|
||||||
|
|
||||||
|
local mission = {
|
||||||
|
id = 'Mission',
|
||||||
|
params = {
|
||||||
|
airborne = true,
|
||||||
|
route = {
|
||||||
|
points = points
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mission
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param groupName string
|
||||||
|
---@param airbase Airbase
|
||||||
|
---@param capZone SpearheadTriggerZone
|
||||||
|
---@param capConfig CapConfig
|
||||||
|
function CAP.getAsMission(groupName, airbase, capZone, capConfig)
|
||||||
|
local points = {
|
||||||
|
[1] = CAP.getAsTasking(groupName, airbase, capZone, capConfig),
|
||||||
|
[2] = Spearhead.classes.capClasses.taskings.RTB.getApproachPoint(airbase, capZone.location, capConfig),
|
||||||
|
[3] = Spearhead.classes.capClasses.taskings.RTB.getInitialPoint(airbase),
|
||||||
|
[4] = Spearhead.classes.capClasses.taskings.RTB.getLandingPoint(airbase)
|
||||||
|
}
|
||||||
|
|
||||||
|
local mission = {
|
||||||
|
id = 'Mission',
|
||||||
|
params = {
|
||||||
|
airborne = true,
|
||||||
|
route = {
|
||||||
|
points = points
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mission
|
||||||
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
---@param groupName string
|
||||||
|
---@param airbase Airbase
|
||||||
|
---@param capZone SpearheadTriggerZone
|
||||||
|
---@param capConfig CapConfig
|
||||||
|
function CAP.getAsTasking(groupName, airbase, capZone, capConfig)
|
||||||
|
local duration = math.random(capConfig:getMinDurationOnStation(), capConfig:getMaxDurationOnStation()) or 1500
|
||||||
|
|
||||||
|
local capTaskingOptions = GetCAPPointFromTriggerZone(airbase, capZone)
|
||||||
|
|
||||||
|
local durationBefore10 = duration - 600
|
||||||
|
if durationBefore10 < 0 then durationBefore10 = 0 end
|
||||||
|
local durationAfter10 = 600
|
||||||
|
if duration < 600 then
|
||||||
|
durationAfter10 = duration
|
||||||
|
end
|
||||||
|
|
||||||
|
local alt = math.random(capConfig:getMinAlt(), capConfig:getMaxAlt())
|
||||||
|
local speed = math.random(capConfig:getMinSpeed(), capConfig:getMaxSpeed())
|
||||||
|
|
||||||
|
return {
|
||||||
|
alt = alt,
|
||||||
|
action = "Turning Point",
|
||||||
|
alt_type = "BARO",
|
||||||
|
speed = speed,
|
||||||
|
ETA = 0,
|
||||||
|
ETA_locked = false,
|
||||||
|
x = capTaskingOptions.closest.x,
|
||||||
|
y = capTaskingOptions.closest.y,
|
||||||
|
speed_locked = true,
|
||||||
|
formation_template = "",
|
||||||
|
task = {
|
||||||
|
id = "ComboTask",
|
||||||
|
params = {
|
||||||
|
tasks = {
|
||||||
|
[1] = {
|
||||||
|
number = 1,
|
||||||
|
auto = false,
|
||||||
|
id = "WrappedAction",
|
||||||
|
enabled = "true",
|
||||||
|
params = {
|
||||||
|
action = {
|
||||||
|
id = "Script",
|
||||||
|
params = {
|
||||||
|
command = "pcall(Spearhead.Events.PublishOnStation, \"" .. groupName .. "\")"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
[2] = {
|
||||||
|
id = 'EngageTargets',
|
||||||
|
params = {
|
||||||
|
maxDist = capConfig:getMaxDeviationRange(),
|
||||||
|
maxDistEnabled = capConfig:getMaxDeviationRange() >= 0, -- required to check maxDist
|
||||||
|
targetTypes = GetCAPTargetTypes(false),
|
||||||
|
priority = 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[3] = {
|
||||||
|
number = 3,
|
||||||
|
auto = false,
|
||||||
|
id = "ControlledTask",
|
||||||
|
enabled = true,
|
||||||
|
params = {
|
||||||
|
task = {
|
||||||
|
id = "Orbit",
|
||||||
|
params = {
|
||||||
|
altitude = alt,
|
||||||
|
pattern = "Anchored",
|
||||||
|
speed = speed,
|
||||||
|
point = {
|
||||||
|
x = capTaskingOptions.closest.x,
|
||||||
|
y = capTaskingOptions.closest.y
|
||||||
|
},
|
||||||
|
speedEdited = true,
|
||||||
|
clockWise = false,
|
||||||
|
hotLegDir = capTaskingOptions.hotLegDir,
|
||||||
|
legLength = capTaskingOptions.legLength,
|
||||||
|
width = capTaskingOptions.width,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
stopCondition = {
|
||||||
|
duration = durationBefore10,
|
||||||
|
condition = "return Spearhead.DcsUtil.NeedsRTBInTen(\"" .. groupName .. "\", 0.10)",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[4] = {
|
||||||
|
number = 4,
|
||||||
|
auto = false,
|
||||||
|
id = "WrappedAction",
|
||||||
|
enabled = "true",
|
||||||
|
params = {
|
||||||
|
action = {
|
||||||
|
id = "Script",
|
||||||
|
params = {
|
||||||
|
command = "pcall(Spearhead.Events.PublishRTBInTen, \"" .. groupName .. "\")"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[5] = {
|
||||||
|
number = 5,
|
||||||
|
auto = false,
|
||||||
|
id = "ControlledTask",
|
||||||
|
enabled = true,
|
||||||
|
params = {
|
||||||
|
task = {
|
||||||
|
id = "Orbit",
|
||||||
|
params = {
|
||||||
|
altitude = alt,
|
||||||
|
pattern = "Circle",
|
||||||
|
speed = speed,
|
||||||
|
-- speedEdited = true,
|
||||||
|
-- clockWise = false,
|
||||||
|
-- hotLegDir = capTaskingOptions.hotLegDir,
|
||||||
|
-- legLength = capTaskingOptions.legLength,
|
||||||
|
-- width = capTaskingOptions.width,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
stopCondition = {
|
||||||
|
duration = durationAfter10,
|
||||||
|
condition = "return Spearhead.DcsUtil.IsBingoFuel(\"" .. groupName .. "\")",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[6] = {
|
||||||
|
number = 6,
|
||||||
|
auto = false,
|
||||||
|
id = "WrappedAction",
|
||||||
|
enabled = "true",
|
||||||
|
params = {
|
||||||
|
action = {
|
||||||
|
id = "Script",
|
||||||
|
params = {
|
||||||
|
command = "pcall(Spearhead.Events.PublishRTB, \"" .. 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.taskings then Spearhead.classes.capClasses.taskings = {} end
|
||||||
|
Spearhead.classes.capClasses.taskings.CAP = CAP
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
|
||||||
|
---@class RTBTasking
|
||||||
|
local RTB = {}
|
||||||
|
|
||||||
|
|
||||||
|
---@param airbase Airbase
|
||||||
|
---@param missionPoint Vec2
|
||||||
|
---@param capConfig CapConfig
|
||||||
|
function RTB.getAsMission(airbase, missionPoint, capConfig)
|
||||||
|
return {
|
||||||
|
id = "Mission",
|
||||||
|
params = {
|
||||||
|
airborne = true,
|
||||||
|
route = {
|
||||||
|
points = {
|
||||||
|
[1] = RTB.getApproachPoint(airbase, missionPoint, capConfig),
|
||||||
|
[2] = RTB.getInitialPoint(airbase),
|
||||||
|
[3] = RTB.getLandingPoint(airbase)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param airbase Airbase
|
||||||
|
---@return number
|
||||||
|
local getRunwayIntoWindCourseRad = function(airbase)
|
||||||
|
|
||||||
|
local activeCourse = nil
|
||||||
|
local minAlignment = nil
|
||||||
|
|
||||||
|
local runways = airbase:getRunways()
|
||||||
|
|
||||||
|
local windVec = atmosphere.getWind(airbase:getPoint())
|
||||||
|
local mPerS = Spearhead.Util.vectorMagnitude(windVec)
|
||||||
|
|
||||||
|
if mPerS > 0 then
|
||||||
|
for i = 1, #runways do
|
||||||
|
local runway = runways[i]
|
||||||
|
|
||||||
|
do --normal
|
||||||
|
local rad = runway.course
|
||||||
|
if rad < 0 then
|
||||||
|
rad = math.abs(rad)
|
||||||
|
else
|
||||||
|
rad = 0 - rad
|
||||||
|
end
|
||||||
|
|
||||||
|
local runwayVec = {x = math.cos(rad), z = math.sin(rad), y = 0}
|
||||||
|
local alignment = Spearhead.Util.vectorAlignment(windVec, runwayVec)
|
||||||
|
|
||||||
|
if minAlignment == nil or alignment < minAlignment then
|
||||||
|
activeCourse = i
|
||||||
|
minAlignment = alignment
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
do --inverse
|
||||||
|
local degree = math.deg(runway.course)
|
||||||
|
degree = (degree + 180) % 360
|
||||||
|
|
||||||
|
local rad = math.rad(degree)
|
||||||
|
if rad < 0 then
|
||||||
|
rad = math.abs(rad)
|
||||||
|
else
|
||||||
|
rad = 0 - rad
|
||||||
|
end
|
||||||
|
|
||||||
|
local runwayVec = {x = math.cos(rad), z = math.sin(rad), y = 0}
|
||||||
|
local alignment = Spearhead.Util.vectorAlignment(windVec, runwayVec)
|
||||||
|
|
||||||
|
if minAlignment == nil or alignment < minAlignment then
|
||||||
|
activeCourse = i
|
||||||
|
minAlignment = alignment
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local rad = runways[1].course
|
||||||
|
|
||||||
|
if activeCourse ~= nil then
|
||||||
|
rad = runways[activeCourse].course
|
||||||
|
end
|
||||||
|
|
||||||
|
if rad < 0 then
|
||||||
|
return math.abs(rad)
|
||||||
|
else
|
||||||
|
return 0 - rad
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
---comment
|
||||||
|
---@param airbase Airbase
|
||||||
|
---@return Vec2
|
||||||
|
---@return number headingFromRunwayDegrees
|
||||||
|
local function calcInitialPoint(airbase)
|
||||||
|
local runwayCourseRad = getRunwayIntoWindCourseRad(airbase)
|
||||||
|
local heading = math.deg(runwayCourseRad)
|
||||||
|
local flipped = (heading + 180) % 360
|
||||||
|
local basePoint = airbase:getPoint()
|
||||||
|
local basePointVec2 = {x = basePoint.x, y = basePoint.z}
|
||||||
|
local point = Spearhead.Util.vectorMove(basePointVec2, flipped, 22000)
|
||||||
|
|
||||||
|
return point, flipped
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---comment
|
||||||
|
---@param airbase Airbase
|
||||||
|
---@param missionPoint Vec2
|
||||||
|
---@param capConfig CapConfig
|
||||||
|
function RTB.getApproachPoint(airbase, missionPoint, capConfig)
|
||||||
|
|
||||||
|
local initialPoint, headingFromRunway = calcInitialPoint(airbase)
|
||||||
|
local pointA = Spearhead.Util.vectorMove(initialPoint, headingFromRunway - 45, 9000)
|
||||||
|
local pointB = Spearhead.Util.vectorMove(initialPoint, headingFromRunway + 45, 9000)
|
||||||
|
|
||||||
|
if Spearhead.Util.VectorDistance2d(missionPoint, pointA) > Spearhead.Util.VectorDistance2d(missionPoint, pointB) then
|
||||||
|
pointA = pointB
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
alt = 3000,
|
||||||
|
action = "Turning Point",
|
||||||
|
alt_type = "BARO",
|
||||||
|
speed = capConfig:getMinSpeed(),
|
||||||
|
ETA = 0,
|
||||||
|
ETA_locked = false,
|
||||||
|
x = pointA.x,
|
||||||
|
y = pointA.y,
|
||||||
|
speed_locked = true,
|
||||||
|
formation_template = "",
|
||||||
|
type = "Turning Point",
|
||||||
|
task = {
|
||||||
|
id = "ComboTask",
|
||||||
|
params = {
|
||||||
|
tasks = {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param airbase Airbase
|
||||||
|
function RTB.getInitialPoint(airbase)
|
||||||
|
local point = calcInitialPoint(airbase)
|
||||||
|
return {
|
||||||
|
alt = 600,
|
||||||
|
action = "Turning Point",
|
||||||
|
alt_type = "BARO",
|
||||||
|
speed = 180,
|
||||||
|
ETA = 0,
|
||||||
|
ETA_locked = false,
|
||||||
|
x = point.x,
|
||||||
|
y = point.y,
|
||||||
|
speed_locked = true,
|
||||||
|
formation_template = "",
|
||||||
|
type = "Turning Point",
|
||||||
|
task = {
|
||||||
|
id = "ComboTask",
|
||||||
|
params = {
|
||||||
|
tasks = {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function RTB.getLandingPoint(airbase)
|
||||||
|
local basePoint = airbase:getPoint()
|
||||||
|
return {
|
||||||
|
alt = basePoint.y,
|
||||||
|
action = "Landing",
|
||||||
|
alt_type = "BARO",
|
||||||
|
speed = 70,
|
||||||
|
ETA = 0,
|
||||||
|
ETA_locked = false,
|
||||||
|
x = basePoint.x,
|
||||||
|
y = basePoint.z,
|
||||||
|
speed_locked = true,
|
||||||
|
formation_template = "",
|
||||||
|
airdromeId = airbase:getID(),
|
||||||
|
type = "Land",
|
||||||
|
task = {
|
||||||
|
id = "ComboTask",
|
||||||
|
params = {
|
||||||
|
tasks = {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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.taskings then Spearhead.classes.capClasses.taskings = {} end
|
||||||
|
Spearhead.classes.capClasses.taskings.RTB = RTB
|
||||||
@@ -1,56 +1,98 @@
|
|||||||
|
|
||||||
|
---@class CapConfig
|
||||||
|
---@field private _isEnabled boolean
|
||||||
|
---@field private _minSpeed number
|
||||||
|
---@field private _maxSpeed number
|
||||||
|
---@field private _minAlt number
|
||||||
|
---@field private _maxAlt number
|
||||||
|
---@field private _minDurationOnStation number
|
||||||
|
---@field private _maxDurationOnStation number
|
||||||
|
---@field private _maxDeviationRange number
|
||||||
|
---@field private _rearmDelay number
|
||||||
|
---@field private _repairDelay number
|
||||||
|
---@field private _deathDelay number
|
||||||
local CapConfig = {};
|
local CapConfig = {};
|
||||||
function CapConfig:new()
|
CapConfig.__index = CapConfig
|
||||||
local o = {}
|
|
||||||
setmetatable(o, { __index = self })
|
---@return CapConfig
|
||||||
|
function CapConfig.new()
|
||||||
|
local self = setmetatable({}, CapConfig)
|
||||||
|
|
||||||
if SpearheadConfig == nil then SpearheadConfig = {} end
|
if SpearheadConfig == nil then SpearheadConfig = {} end
|
||||||
if SpearheadConfig.CapConfig == nil then SpearheadConfig.CapConfig = {} end
|
if SpearheadConfig.CapConfig == nil then SpearheadConfig.CapConfig = {} end
|
||||||
|
|
||||||
local enabled = SpearheadConfig.CapConfig.enabled
|
local enabled = SpearheadConfig.CapConfig.enabled
|
||||||
if enabled == nil then enabled = true end
|
if enabled == nil then enabled = true end
|
||||||
---@return boolean
|
self._isEnabled = enabled
|
||||||
o.isEnabled = function(self) return enabled == true end
|
|
||||||
|
|
||||||
local minSpeed = (tonumber(SpearheadConfig.CapConfig.minSpeed) or 400) * 0.514444
|
self._minSpeed = (tonumber(SpearheadConfig.CapConfig.minSpeed) or 400) * 0.514444
|
||||||
---@return number
|
self._maxSpeed = (tonumber(SpearheadConfig.CapConfig.maxSpeed) or 400) * 0.514444
|
||||||
o.getMinSpeed = function(self) return minSpeed end
|
|
||||||
|
|
||||||
local maxSpeed = (tonumber(SpearheadConfig.CapConfig.maxSpeed) or 400) * 0.514444
|
self._minAlt = (tonumber(SpearheadConfig.CapConfig.minAlt) or 18000) * 0.3048
|
||||||
---@return number
|
self._maxAlt = (tonumber(SpearheadConfig.CapConfig.maxAlt) or 28000) * 0.3048
|
||||||
o.getMaxSpeed = function(self) return maxSpeed end
|
|
||||||
|
|
||||||
local minAlt = (tonumber(SpearheadConfig.CapConfig.minAlt) or 18000) * 0.3048
|
self._minDurationOnStation = 1200
|
||||||
---@return number
|
self._maxDurationOnStation = 2700
|
||||||
o.getMinAlt = function(self) return minAlt end
|
|
||||||
|
|
||||||
local maxAlt = (tonumber(SpearheadConfig.CapConfig.maxAlt) or 28000) * 0.3048
|
self._maxDeviationRange = 35 * 1852 -- in meters
|
||||||
---@return number
|
self._rearmDelay = tonumber(SpearheadConfig.CapConfig.rearmDelay) or 600
|
||||||
o.getMaxAlt = function(self) return maxAlt end
|
self._repairDelay = tonumber(SpearheadConfig.CapConfig.repairDelay) or 600
|
||||||
|
self._deathDelay = tonumber(SpearheadConfig.CapConfig.deathDelay) or 1800
|
||||||
|
|
||||||
local minDurationOnStation = 1200
|
return self;
|
||||||
---@return number
|
end
|
||||||
o.getMinDurationOnStation = function(self) return minDurationOnStation end
|
|
||||||
|
|
||||||
local maxDurationOnStation = 2700
|
function CapConfig:isEnabled()
|
||||||
---@return number
|
return self._isEnabled
|
||||||
o.getmaxDurationOnStation = function(self) return maxDurationOnStation end
|
end
|
||||||
|
|
||||||
local maxDeviationRange = 20 * 1852;
|
---@return number
|
||||||
---@return number
|
function CapConfig:getMinSpeed()
|
||||||
o.getMaxDeviationRange = function(self) return maxDeviationRange end
|
return self._minSpeed
|
||||||
|
end
|
||||||
|
|
||||||
local rearmDelay = tonumber(SpearheadConfig.CapConfig.rearmDelay) or 600
|
---@return number
|
||||||
---@return number
|
function CapConfig:getMaxSpeed()
|
||||||
o.getRearmDelay = function(self) return rearmDelay end
|
return self._maxSpeed
|
||||||
|
end
|
||||||
|
|
||||||
local deathDelay = tonumber(SpearheadConfig.CapConfig.deathDelay) or 1800
|
---@return number
|
||||||
---@return number
|
function CapConfig:getMinAlt()
|
||||||
o.getDeathDelay = function(self) return deathDelay end
|
return self._minAlt
|
||||||
o.logLevel = "INFO"
|
end
|
||||||
|
|
||||||
return o;
|
---@return number
|
||||||
|
function CapConfig:getMaxAlt()
|
||||||
|
return self._maxAlt
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return number
|
||||||
|
function CapConfig:getMinDurationOnStation()
|
||||||
|
return self._minDurationOnStation
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return number
|
||||||
|
function CapConfig:getMaxDurationOnStation()
|
||||||
|
return self._maxDurationOnStation
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return number
|
||||||
|
function CapConfig:getMaxDeviationRange()
|
||||||
|
return self._maxDeviationRange
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return number
|
||||||
|
function CapConfig:getRearmDelay()
|
||||||
|
return self._rearmDelay
|
||||||
|
end
|
||||||
|
|
||||||
|
function CapConfig:getRepairDelay()
|
||||||
|
return self._repairDelay
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return number
|
||||||
|
function CapConfig:getDeathDelay()
|
||||||
|
return self._deathDelay
|
||||||
end
|
end
|
||||||
|
|
||||||
if not Spearhead.internal then Spearhead.internal = {} end
|
if not Spearhead.internal then Spearhead.internal = {} end
|
||||||
|
|||||||
+104
-10
@@ -36,15 +36,15 @@ do -- INIT UTIL
|
|||||||
|
|
||||||
---@param orig table
|
---@param orig table
|
||||||
---@return table copy
|
---@return table copy
|
||||||
function UTIL.copyTable(orig)
|
function UTIL.deepCopyTable(orig)
|
||||||
local orig_type = type(orig)
|
local orig_type = type(orig)
|
||||||
local copy
|
local copy
|
||||||
if orig_type == 'table' then
|
if orig_type == 'table' then
|
||||||
copy = {}
|
copy = {}
|
||||||
for orig_key, orig_value in next, orig, nil do
|
for orig_key, orig_value in next, orig, nil do
|
||||||
copy[UTIL.copyTable(orig_key)] = UTIL.copyTable(orig_value)
|
copy[UTIL.deepCopyTable(orig_key)] = UTIL.deepCopyTable(orig_value)
|
||||||
end
|
end
|
||||||
setmetatable(copy, UTIL.copyTable(getmetatable(orig)))
|
setmetatable(copy, UTIL.deepCopyTable(getmetatable(orig)))
|
||||||
else -- number, string, boolean, etc
|
else -- number, string, boolean, etc
|
||||||
copy = orig
|
copy = orig
|
||||||
end
|
end
|
||||||
@@ -88,15 +88,15 @@ do -- INIT UTIL
|
|||||||
table.insert(sb, string.rep(" ", indent)) -- indent it
|
table.insert(sb, string.rep(" ", indent)) -- indent it
|
||||||
if type(value) == "table" and not done[value] then
|
if type(value) == "table" and not done[value] then
|
||||||
done[value] = true
|
done[value] = true
|
||||||
table.insert(sb, key .. " = {\n");
|
table.insert(sb, "[\"" ..key .. "\"]" .. " = {\n");
|
||||||
table.insert(sb, table_print(value, indent + 2, done))
|
table.insert(sb, table_print(value, indent + 2, done))
|
||||||
table.insert(sb, string.rep(" ", indent)) -- indent it
|
table.insert(sb, string.rep(" ", indent)) -- indent it
|
||||||
table.insert(sb, "}\n");
|
table.insert(sb, "},\n");
|
||||||
elseif "number" == type(key) then
|
elseif "number" == type(key) then
|
||||||
table.insert(sb, string.format("\"%s\"\n", tostring(value)))
|
table.insert(sb, string.format("\"%s\",\n", tostring(value)))
|
||||||
else
|
else
|
||||||
table.insert(sb, string.format(
|
table.insert(sb, string.format(
|
||||||
"%s = \"%s\"\n", tostring(key), tostring(value)))
|
"[\"%s\"] = \"%s\",\n", tostring(key), tostring(value)))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return table.concat(sb)
|
return table.concat(sb)
|
||||||
@@ -172,6 +172,54 @@ do -- INIT UTIL
|
|||||||
return (vec.x ^ 2 + vec.y ^ 2 + vec.z ^ 2) ^ 0.5
|
return (vec.x ^ 2 + vec.y ^ 2 + vec.z ^ 2) ^ 0.5
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param vec Vec3
|
||||||
|
---@return Vec3
|
||||||
|
function UTIL.vectorNormalize(vec)
|
||||||
|
local magnitude = UTIL.vectorMagnitude(vec)
|
||||||
|
if magnitude == 0 then
|
||||||
|
return { x = 0, y = 0, z = 0 }
|
||||||
|
end
|
||||||
|
return { x = vec.x / magnitude, y = vec.y / magnitude, z = vec.z / magnitude }
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param vec Vec2
|
||||||
|
---@param direction number @in degrees
|
||||||
|
---@param distance number
|
||||||
|
---@return Vec2
|
||||||
|
function UTIL.vectorMove(vec, direction, distance)
|
||||||
|
local rad = math.rad(direction)
|
||||||
|
local x = vec.x + (math.cos(rad) * distance)
|
||||||
|
local y = vec.y + (math.sin(rad) * distance)
|
||||||
|
|
||||||
|
return { x = x, y = y}
|
||||||
|
end
|
||||||
|
|
||||||
|
---comment
|
||||||
|
---@param vec1 Vec2
|
||||||
|
---@param vec2 Vec2
|
||||||
|
---@return number in degrees
|
||||||
|
function UTIL.vectorHeadingFromTo(vec1, vec2)
|
||||||
|
local dx = vec2.x - vec1.x
|
||||||
|
local dy = vec2.y - vec1.y
|
||||||
|
local heading = math.deg(math.atan2(dy, dx))
|
||||||
|
if heading < 0 then
|
||||||
|
heading = heading + 360
|
||||||
|
end
|
||||||
|
return heading
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
---@param vec1 Vec3
|
||||||
|
---@param vec2 Vec3
|
||||||
|
---@return number alignment a number in range [-1,1]. > 0 same direction. < 0 opposite direction
|
||||||
|
function UTIL.vectorAlignment(vec1, vec2)
|
||||||
|
local vec1Norm = UTIL.vectorNormalize(vec1)
|
||||||
|
local vec2Norm = UTIL.vectorNormalize(vec2)
|
||||||
|
|
||||||
|
return ((vec1Norm.x * vec2Norm.x) + (vec1Norm.y * vec2Norm.y) + (vec1Norm.z * vec2Norm.z))
|
||||||
|
end
|
||||||
|
|
||||||
local function isInComplexPolygon(polygon, x, y)
|
local function isInComplexPolygon(polygon, x, y)
|
||||||
local function getEdges(poly)
|
local function getEdges(poly)
|
||||||
local result = {}
|
local result = {}
|
||||||
@@ -706,7 +754,7 @@ do -- INIT DCS_UTIL
|
|||||||
---@param groupName string
|
---@param groupName string
|
||||||
function DCS_UTIL.IsGroupStatic(groupName)
|
function DCS_UTIL.IsGroupStatic(groupName)
|
||||||
if DCS_UTIL.__miz_groups[groupName] then
|
if DCS_UTIL.__miz_groups[groupName] then
|
||||||
return DCS_UTIL.__miz_groups[groupName].category == DCS_UTIL.GroupCategory.STATIC;
|
return DCS_UTIL.__miz_groups[groupName].category == DCS_UTIL.GroupCategory.STATIC
|
||||||
end
|
end
|
||||||
|
|
||||||
return StaticObject.getByName(groupName) ~= nil
|
return StaticObject.getByName(groupName) ~= nil
|
||||||
@@ -1255,9 +1303,10 @@ do -- INIT DCS_UTIL
|
|||||||
---@param route table? route of the group. If nil wil be the default route.
|
---@param route table? route of the group. If nil wil be the default route.
|
||||||
---@param uncontrolled boolean? Sets the group to be uncontrolled on spawn
|
---@param uncontrolled boolean? Sets the group to be uncontrolled on spawn
|
||||||
---@param countryId CountryID? Overwrites the country
|
---@param countryId CountryID? Overwrites the country
|
||||||
|
---@param emptyLoadouts boolean? If true, the group will spawn with empty loadouts
|
||||||
---@return table? new_group the Group class that was spawned
|
---@return table? new_group the Group class that was spawned
|
||||||
---@return boolean? isStatic whether the group is a static or not
|
---@return boolean? isStatic whether the group is a static or not
|
||||||
function DCS_UTIL.SpawnGroupTemplate(groupName, location, route, uncontrolled, countryId)
|
function DCS_UTIL.SpawnGroupTemplate(groupName, location, route, uncontrolled, countryId, emptyLoadouts)
|
||||||
if groupName == nil then
|
if groupName == nil then
|
||||||
return nil, nil
|
return nil, nil
|
||||||
end
|
end
|
||||||
@@ -1280,7 +1329,7 @@ do -- INIT DCS_UTIL
|
|||||||
return object, true
|
return object, true
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
local spawn_template = template.group_template
|
local spawn_template = Spearhead.Util.deepCopyTable(template.group_template)
|
||||||
if location ~= nil then
|
if location ~= nil then
|
||||||
local x_offset
|
local x_offset
|
||||||
if location.x ~= nil then x_offset = spawn_template.x - location.x end
|
if location.x ~= nil then x_offset = spawn_template.x - location.x end
|
||||||
@@ -1298,6 +1347,26 @@ do -- INIT DCS_UTIL
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if spawn_template.units then
|
||||||
|
for _, unit in pairs(spawn_template.units) do
|
||||||
|
if unit["parking"] then
|
||||||
|
unit["parking_landing"] = unit["parking"]
|
||||||
|
end
|
||||||
|
|
||||||
|
if unit["parking_id"] then
|
||||||
|
unit["parking_landing_id"] = unit["parking_id"]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if emptyLoadouts == true then
|
||||||
|
for _, unit in pairs(spawn_template.units) do
|
||||||
|
if unit["payload"] and unit["payload"]["pylons"] then
|
||||||
|
unit["payload"]["pylons"] = {}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if route ~= nil then
|
if route ~= nil then
|
||||||
spawn_template.route = route
|
spawn_template.route = route
|
||||||
end
|
end
|
||||||
@@ -1305,6 +1374,7 @@ do -- INIT DCS_UTIL
|
|||||||
if uncontrolled ~= nil then
|
if uncontrolled ~= nil then
|
||||||
spawn_template.uncontrolled = uncontrolled
|
spawn_template.uncontrolled = uncontrolled
|
||||||
end
|
end
|
||||||
|
|
||||||
local country = countryId or template.country_id
|
local country = countryId or template.country_id
|
||||||
local new_group = coalition.addGroup(country, template.category, spawn_template)
|
local new_group = coalition.addGroup(country, template.category, spawn_template)
|
||||||
return new_group, false
|
return new_group, false
|
||||||
@@ -1320,6 +1390,30 @@ do -- INIT DCS_UTIL
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function DCS_UTIL.NeedsRTBInTen(groupName, fuelOffset)
|
||||||
|
|
||||||
|
local isBingo = Spearhead.DcsUtil.IsBingoFuel(groupName, fuelOffset)
|
||||||
|
if isBingo then return true end
|
||||||
|
|
||||||
|
local aliveUnits = 0
|
||||||
|
local group = Group.getByName(groupName)
|
||||||
|
if group then
|
||||||
|
for _ , unit in pairs(group:getUnits()) do
|
||||||
|
if unit and unit:isExist() == true and unit:inAir() == true then
|
||||||
|
aliveUnits = aliveUnits + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if aliveUnits / group:getInitialSize() <= 0.5 then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return boolean
|
||||||
function DCS_UTIL.IsBingoFuel(groupName, offset)
|
function DCS_UTIL.IsBingoFuel(groupName, offset)
|
||||||
if offset == nil then offset = 0 end
|
if offset == nil then offset = 0 end
|
||||||
local bingoSetting = 0.20
|
local bingoSetting = 0.20
|
||||||
|
|||||||
+38
-110
@@ -8,7 +8,7 @@
|
|||||||
---@field StageZonesByNumber table<string, Array<string>> Stage zones grouped by index number
|
---@field StageZonesByNumber table<string, Array<string>> Stage zones grouped by index number
|
||||||
---@field AllFarpZones Array<string>
|
---@field AllFarpZones Array<string>
|
||||||
---@field CapRoutes Array<string> All Cap route zone names
|
---@field CapRoutes Array<string> All Cap route zone names
|
||||||
---@field CapDataPerStageNumber table<integer, CapData> table<StageNumber, table>
|
---@field capZonesByCapZoneID table<string, CapRoute> table<StageNumber, table>
|
||||||
---@field CarrierRouteZones Array<string> All Carrier routes zones
|
---@field CarrierRouteZones Array<string> All Carrier routes zones
|
||||||
---@field BlueSams Array<string> All blue sam zones
|
---@field BlueSams Array<string> All blue sam zones
|
||||||
---@field SupplyHubZones Array<string> All supply hub zones
|
---@field SupplyHubZones Array<string> All supply hub zones
|
||||||
@@ -19,13 +19,10 @@
|
|||||||
---@field FarpZoneData table<string,FarpZoneData>
|
---@field FarpZoneData table<string,FarpZoneData>
|
||||||
---@field missionCodes table<string, boolean>
|
---@field missionCodes table<string, boolean>
|
||||||
|
|
||||||
---@class CapData
|
|
||||||
---@field routes Array<CapRoute>
|
|
||||||
---@field current integer
|
|
||||||
|
|
||||||
---@class CapRoute
|
---@class CapRoute
|
||||||
---@field point1 Vec3
|
---@field zones Array<SpearheadTriggerZone>
|
||||||
---@field point2 Vec3?
|
---@field current number
|
||||||
|
|
||||||
---@class MissionAnnotations
|
---@class MissionAnnotations
|
||||||
---@field description string?
|
---@field description string?
|
||||||
@@ -82,7 +79,7 @@ function Database.New(Logger)
|
|||||||
AllZoneNames = {},
|
AllZoneNames = {},
|
||||||
BlueSams = {},
|
BlueSams = {},
|
||||||
CapRoutes = {},
|
CapRoutes = {},
|
||||||
CapDataPerStageNumber = {},
|
capZonesByCapZoneID = {},
|
||||||
CarrierRouteZones = {},
|
CarrierRouteZones = {},
|
||||||
MissionZones = {},
|
MissionZones = {},
|
||||||
MissionZonesLocations = {},
|
MissionZonesLocations = {},
|
||||||
@@ -367,55 +364,21 @@ function Database.New(Logger)
|
|||||||
self:loadMiscGroupsInStages()
|
self:loadMiscGroupsInStages()
|
||||||
|
|
||||||
|
|
||||||
for _, zoneData in pairs(self._tables.StageZones) do
|
for _, cap_route_zone in pairs(self._tables.CapRoutes) do
|
||||||
local number = zoneData.StageIndex
|
local split = Spearhead.Util.split_string(cap_route_zone, "_")
|
||||||
|
local zoneID = split[2]
|
||||||
|
|
||||||
for _, cap_route_zone in pairs(self._tables.CapRoutes) do
|
if zoneID then
|
||||||
if Spearhead.DcsUtil.isZoneInZone(cap_route_zone, zoneData.StageZoneName) == true then
|
if tables.capZonesByCapZoneID[zoneID] == nil then
|
||||||
local zone = Spearhead.DcsUtil.getZoneByName(cap_route_zone)
|
tables.capZonesByCapZoneID[zoneID] = {
|
||||||
if zone then
|
zones = {},
|
||||||
---@type CapData
|
current = 1
|
||||||
local capData = self._tables.CapDataPerStageNumber[number]
|
}
|
||||||
if capData == nil then
|
end
|
||||||
capData = {
|
|
||||||
routes = {},
|
|
||||||
current = 0
|
|
||||||
}
|
|
||||||
self._tables.CapDataPerStageNumber[number] = capData
|
|
||||||
end
|
|
||||||
|
|
||||||
|
local zone = Spearhead.DcsUtil.getZoneByName(cap_route_zone)
|
||||||
if zone.zone_type == Spearhead.DcsUtil.ZoneType.Cilinder then
|
if zone then
|
||||||
table.insert(capData.routes,
|
table.insert(tables.capZonesByCapZoneID[zoneID].zones, zone)
|
||||||
{ point1 = { x = zone.location.x, z = zone.location.y, y = 0 }, point2 = nil })
|
|
||||||
else
|
|
||||||
local biggest = nil
|
|
||||||
local biggestA = nil
|
|
||||||
local biggestB = nil
|
|
||||||
|
|
||||||
for i = 1, 3 do
|
|
||||||
for ii = i + 1, 4 do
|
|
||||||
local a = zone.verts[i]
|
|
||||||
local b = zone.verts[ii]
|
|
||||||
local dist = Spearhead.Util.VectorDistance2d(a, b)
|
|
||||||
|
|
||||||
if biggest == nil or dist > biggest then
|
|
||||||
biggestA = a
|
|
||||||
biggestB = b
|
|
||||||
biggest = dist
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if biggestA and biggestB then
|
|
||||||
table.insert(capData.routes,
|
|
||||||
{
|
|
||||||
point1 = { x = biggestA.x, z = biggestA.y },
|
|
||||||
point2 = { x = biggestB.x, z = biggestB.y }
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -842,66 +805,31 @@ function Database:GetLocationForMissionZone(missionZoneName)
|
|||||||
return self._tables.MissionZonesLocations[missionZoneName]
|
return self._tables.MissionZonesLocations[missionZoneName]
|
||||||
end
|
end
|
||||||
|
|
||||||
---comment
|
---@param zoneID string
|
||||||
---@param stageNumber string
|
---@return SpearheadTriggerZone?
|
||||||
---@param baseName string
|
function Database:GetCapZoneForZoneID(zoneID)
|
||||||
---@return table?
|
zoneID = tostring(zoneID) or "nothing"
|
||||||
function Database:getCapRouteInZone(stageNumber, baseName)
|
|
||||||
local stageNumber = tostring(stageNumber) or "nothing"
|
|
||||||
local routeData = self._tables.CapDataPerStageNumber[stageNumber]
|
|
||||||
if routeData then
|
|
||||||
local count = Spearhead.Util.tableLength(routeData.routes)
|
|
||||||
if count > 0 then
|
|
||||||
routeData.current = routeData.current + 1
|
|
||||||
if count < routeData.current then
|
|
||||||
routeData.current = 1
|
|
||||||
end
|
|
||||||
return routeData.routes[routeData.current]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
do
|
|
||||||
local function GetClosestPointOnCircle(pC, radius, p)
|
|
||||||
local vX = p.x - pC.x;
|
|
||||||
local vY = p.z - pC.z;
|
|
||||||
local magV = math.sqrt(vX * vX + vY * vY);
|
|
||||||
local aX = pC.x + vX / magV * radius;
|
|
||||||
local aY = pC.z + vY / magV * radius;
|
|
||||||
return { x = aX, z = aY }
|
|
||||||
end
|
|
||||||
local stageZoneName = Spearhead.Util.randomFromList(self._tables.StageZonesByNumber[stageNumber]) or "none"
|
|
||||||
local stagezone = Spearhead.DcsUtil.getZoneByName(stageZoneName)
|
|
||||||
if stagezone then
|
|
||||||
---@type Airbase?
|
|
||||||
local base = Airbase.getByName(baseName)
|
|
||||||
if base then
|
|
||||||
local closest = nil
|
|
||||||
if stagezone.zone_type == Spearhead.DcsUtil.ZoneType.Cilinder then
|
|
||||||
closest = GetClosestPointOnCircle({ x = stagezone.location.x, z = stagezone.location.y },
|
|
||||||
stagezone.radius,
|
|
||||||
base:getPoint())
|
|
||||||
else
|
|
||||||
local closestDistance = -1
|
|
||||||
for _, vert in pairs(stagezone.verts) do
|
|
||||||
local pos = base:getPoint()
|
|
||||||
local vec2 = { x = pos.x, y = pos.z }
|
|
||||||
local distance = Spearhead.Util.VectorDistance2d(vert, vec2)
|
|
||||||
if closestDistance == -1 or distance < closestDistance then
|
|
||||||
closestDistance = distance
|
|
||||||
closest = vert
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if math.random(1, 2) % 2 == 0 then
|
local capZonesForID = self._tables.capZonesByCapZoneID[zoneID]
|
||||||
return { point1 = closest, point2 = { x = stagezone.location.x, z = stagezone.location.y } }
|
|
||||||
else
|
if capZonesForID and capZonesForID.zones then
|
||||||
return { point1 = { x = stagezone.location.x, z = stagezone.location.y }, point2 = closest }
|
|
||||||
end
|
local count = Spearhead.Util.tableLength(capZonesForID.zones)
|
||||||
end
|
if count == 0 then
|
||||||
|
self._logger:warn("Tried to get cap zone for zoneID: " .. zoneID .. " but cap zones were empty for this ID")
|
||||||
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
return nil
|
capZonesForID.current = capZonesForID.current + 1
|
||||||
|
if Spearhead.Util.tableLength(capZonesForID.zones) < capZonesForID.current then
|
||||||
|
capZonesForID.current = 1
|
||||||
|
end
|
||||||
|
return capZonesForID.zones[capZonesForID.current]
|
||||||
|
else
|
||||||
|
self._logger:warn("Tried to get cap zone for zoneID: " .. zoneID .. " but no cap zones were found for this ID")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return Array<string> result a list of stage zone names
|
---@return Array<string> result a list of stage zone names
|
||||||
|
|||||||
@@ -159,14 +159,17 @@ function StageBase:SpawnBlueUnits()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function StageBase:ActivateRedStage()
|
function StageBase:ActivateRedStage()
|
||||||
if self._initialSide == 2 and self._airbase then
|
self._logger:debug("Activate red stage for airbase: " .. self._airbase:getName())
|
||||||
self._airbase:setCoalition(1)
|
if self._airbase and (self._initialSide == 2 or self._initialSide == 1) then
|
||||||
|
self._airbase:setCoalition(coalition.side.RED)
|
||||||
self._airbase:autoCapture(false)
|
self._airbase:autoCapture(false)
|
||||||
end
|
end
|
||||||
self:SpawnRedUnits()
|
self:SpawnRedUnits()
|
||||||
end
|
end
|
||||||
|
|
||||||
function StageBase:ActivateBlueStage()
|
function StageBase:ActivateBlueStage()
|
||||||
|
self._logger:debug("Activate blue stage for airbase: " .. self._airbase:getName())
|
||||||
|
|
||||||
self:CleanRedUnits()
|
self:CleanRedUnits()
|
||||||
|
|
||||||
if self._buildableMission and self._buildableMission:getState() ~= "COMPLETED" then
|
if self._buildableMission and self._buildableMission:getState() ~= "COMPLETED" then
|
||||||
@@ -178,7 +181,8 @@ end
|
|||||||
|
|
||||||
function StageBase:FinaliseBlueStage()
|
function StageBase:FinaliseBlueStage()
|
||||||
if self._initialSide == 2 and self._airbase then
|
if self._initialSide == 2 and self._airbase then
|
||||||
self._airbase:setCoalition(2)
|
self._airbase:setCoalition(coalition.side.BLUE)
|
||||||
|
self._airbase:autoCapture(false)
|
||||||
end
|
end
|
||||||
|
|
||||||
self:SpawnBlueUnits()
|
self:SpawnBlueUnits()
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
local BattleManager = {}
|
local BattleManager = {}
|
||||||
BattleManager.__index = BattleManager
|
BattleManager.__index = BattleManager
|
||||||
|
|
||||||
|
local debugDrawing = false
|
||||||
|
|
||||||
---@param redGroups Array<SpearheadGroup>
|
---@param redGroups Array<SpearheadGroup>
|
||||||
---@param blueGroups Array<SpearheadGroup>
|
---@param blueGroups Array<SpearheadGroup>
|
||||||
---@param name string
|
---@param name string
|
||||||
@@ -131,12 +133,10 @@ function BattleManager:LetUnitsShoot(groups, targetGroups)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if SpearheadConfig and SpearheadConfig.debugEnabled == true then
|
if debugDrawing == true then
|
||||||
self:DrawDebugLine(point, unit)
|
self:DrawDebugLine(point, unit)
|
||||||
end
|
end
|
||||||
|
|
||||||
self._logger:debug("Red unit " .. unit:getName() .. " will shoot " .. qty .. " rounds at point: " .. tostring(point))
|
|
||||||
|
|
||||||
local controller = unit:getController()
|
local controller = unit:getController()
|
||||||
if controller then
|
if controller then
|
||||||
controller:setTask(shootTask)
|
controller:setTask(shootTask)
|
||||||
@@ -241,7 +241,7 @@ function BattleManager:GetRandomPoint(origin, groupHulls)
|
|||||||
if not hull then return nil end
|
if not hull then return nil end
|
||||||
local shootPoints = Spearhead.Util.GetTangentHullPointsFromOrigin(hull, origin)
|
local shootPoints = Spearhead.Util.GetTangentHullPointsFromOrigin(hull, origin)
|
||||||
|
|
||||||
if SpearheadConfig and SpearheadConfig.debugEnabled == true then
|
if debugDrawing == true then
|
||||||
self:DrawDebugZone({ hull })
|
self:DrawDebugZone({ hull })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -271,8 +271,8 @@ function MissionCommandsHelper:AddAllMissionCommandsToGroup(groupID)
|
|||||||
local mission = self.missionsByCode[code]
|
local mission = self.missionsByCode[code]
|
||||||
if mission and mission.priority == "primary" then
|
if mission and mission.priority == "primary" then
|
||||||
count = count + 1
|
count = count + 1
|
||||||
if count <= perFolder then
|
if count <= perFolder then
|
||||||
local copied = Spearhead.Util.copyTable(path)
|
local copied = Spearhead.Util.deepCopyTable(path)
|
||||||
self:addMissionCommands(groupID, copied, mission)
|
self:addMissionCommands(groupID, copied, mission)
|
||||||
else
|
else
|
||||||
local name = "Next Menu ..."
|
local name = "Next Menu ..."
|
||||||
@@ -294,7 +294,7 @@ function MissionCommandsHelper:AddAllMissionCommandsToGroup(groupID)
|
|||||||
if mission and mission.priority == "secondary" then
|
if mission and mission.priority == "secondary" then
|
||||||
count = count + 1
|
count = count + 1
|
||||||
if count <= perFolder then
|
if count <= perFolder then
|
||||||
local copied = Spearhead.Util.copyTable(path)
|
local copied = Spearhead.Util.deepCopyTable(path)
|
||||||
self:addMissionCommands(groupID, copied, mission)
|
self:addMissionCommands(groupID, copied, mission)
|
||||||
else
|
else
|
||||||
local name = "Next Menu ..."
|
local name = "Next Menu ..."
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ function SupplyUnitsTracker.getOrCreate(logLevel)
|
|||||||
singleton._supplyUnitsByName = {}
|
singleton._supplyUnitsByName = {}
|
||||||
singleton._droppedCrates = {}
|
singleton._droppedCrates = {}
|
||||||
singleton._registeredHubs = {}
|
singleton._registeredHubs = {}
|
||||||
|
singleton._supplyUnitSpawnedListener = {}
|
||||||
|
|
||||||
singleton._commandsHelper = Spearhead.classes.stageClasses.helpers.MissionCommandsHelper.getOrCreate(singleton._logger.LogLevel)
|
singleton._commandsHelper = Spearhead.classes.stageClasses.helpers.MissionCommandsHelper.getOrCreate(singleton._logger.LogLevel)
|
||||||
|
|
||||||
|
|||||||
+16
-3
@@ -25,13 +25,26 @@ SpearheadConfig = {
|
|||||||
-- unit: feet
|
-- unit: feet
|
||||||
maxAlt = 28000, -- default 28000
|
maxAlt = 28000, -- default 28000
|
||||||
|
|
||||||
|
-- DELAYS.
|
||||||
|
-- Delays work as follow.
|
||||||
|
-- When an aircraft lands alive and well it will be rearmed and ready to go.
|
||||||
|
-- When part of the flight died it will be repaired and then rearmed. (both delays)
|
||||||
|
-- When the entire group dies it will first wait for the deathDelay, then it will be repaired and rearmed.
|
||||||
|
-- while rearming: Spawned on the ramp.
|
||||||
|
-- while repairing: Spawned on the ramp.
|
||||||
|
-- while deathDelay no unit is spawned.
|
||||||
|
|
||||||
--Delay for aircraft from touchdown to off the chocks.
|
--Delay for aircraft from touchdown to off the chocks.
|
||||||
-- unit: seconds
|
-- unit: seconds
|
||||||
rearmDelay = 600, -- default 600
|
rearmDelay = 600, -- default 600
|
||||||
|
|
||||||
--Delay for aircraft from death to takeoff.
|
-- Delay for aircraft that has died to be repaired before rearming starts.
|
||||||
--When the seconds remaining is the same at the rearmDelay it will be spawned on the ramp and follow the rearm logic.
|
-- unit: seconds
|
||||||
-- !! Can not be lower than rearmDelay
|
repairDelay = 600, -- default 600
|
||||||
|
|
||||||
|
--Delay for aircraft before the repairing and rearming cycles begin.
|
||||||
|
-- applied when the whole group dies.
|
||||||
|
-- (see this as an additional bonus delay for destroying an entire group)
|
||||||
-- unit: seconds
|
-- unit: seconds
|
||||||
deathDelay = 1800, -- default 1800
|
deathDelay = 1800, -- default 1800
|
||||||
},
|
},
|
||||||
|
|||||||
+4
-1
@@ -44,7 +44,10 @@ assert(loadfile(classPath .. "stageClasses\\SpecialZones\\BlueSam.lua"))()
|
|||||||
assert(loadfile(classPath .. "stageClasses\\SpecialZones\\FarpZone.lua"))()
|
assert(loadfile(classPath .. "stageClasses\\SpecialZones\\FarpZone.lua"))()
|
||||||
assert(loadfile(classPath .. "stageClasses\\SpecialZones\\SupplyHub.lua"))()
|
assert(loadfile(classPath .. "stageClasses\\SpecialZones\\SupplyHub.lua"))()
|
||||||
|
|
||||||
assert(loadfile(classPath .. "capClasses\\CapGroup.lua"))()
|
assert(loadfile(classPath .. "capClasses\\taskings\\RTB.lua"))()
|
||||||
|
assert(loadfile(classPath .. "capClasses\\taskings\\CAP.lua"))()
|
||||||
|
assert(loadfile(classPath .. "capClasses\\airGroups\\AirGroup.lua"))()
|
||||||
|
assert(loadfile(classPath .. "capClasses\\airGroups\\CapGroup.lua"))()
|
||||||
assert(loadfile(classPath .. "capClasses\\GlobalCapManager.lua"))()
|
assert(loadfile(classPath .. "capClasses\\GlobalCapManager.lua"))()
|
||||||
assert(loadfile(classPath .. "capClasses\\CapAirbase.lua"))()
|
assert(loadfile(classPath .. "capClasses\\CapAirbase.lua"))()
|
||||||
assert(loadfile(classPath .. "capClasses\\runwayBombing\\RunwayBombingTracker.lua"))()
|
assert(loadfile(classPath .. "capClasses\\runwayBombing\\RunwayBombingTracker.lua"))()
|
||||||
|
|||||||
+2
-2
@@ -25,8 +25,8 @@ SpearheadConfig = {
|
|||||||
|
|
||||||
--Delay for aircraft from touchdown to off the chocks.
|
--Delay for aircraft from touchdown to off the chocks.
|
||||||
-- unit: seconds
|
-- unit: seconds
|
||||||
rearmDelay = 600, -- default 600
|
rearmDelay = 180, -- default 600
|
||||||
|
repairDelay = 600, -- default 600
|
||||||
--Delay for aircraft from death to takeoff.
|
--Delay for aircraft from death to takeoff.
|
||||||
--When the seconds remaining is the same at the rearmDelay it will be spawned on the ramp and follow the rearm logic.
|
--When the seconds remaining is the same at the rearmDelay it will be spawned on the ramp and follow the rearm logic.
|
||||||
-- !! Can not be lower than rearmDelay
|
-- !! Can not be lower than rearmDelay
|
||||||
|
|||||||
Reference in New Issue
Block a user