Sweep cap (#54)
* Added initial implementation of Sweep groups * Added initial implementation of Sweep groups * Added initial implementation of Sweep groups * Added initial implementation of Sweep groups * fixed issues when testing * Intercept cap (#55) * First intial WIP for intercept units * First intial WIP for intercept units * major updates --------- Co-authored-by: ex61wi <tim.rorije@ing.com> Co-authored-by: dutchie032 <dutchie032> --------- Co-authored-by: ex61wi <tim.rorije@ing.com> Co-authored-by: dutchie032 <dutchie032>
This commit is contained in:
committed by
GitHub
co-authored by
ex61wi
dutchie032 <dutchie032>
parent
bdf7154333
commit
ef65f5a353
@@ -2,16 +2,25 @@
|
||||
---@field private airbaseName string
|
||||
---@field private logger table
|
||||
---@field private database Database
|
||||
---@field private detectionManager DetectionManager
|
||||
---@field private activeStage number
|
||||
---@field private capConfig table
|
||||
---@field private capGroupsByName table<string, CapGroup>
|
||||
---@field private sweepGroupsByName table<string, SweepGroup>
|
||||
---@field private interceptGroupsByName table<string, InterceptGroup>
|
||||
---@field private runwayBombingTracker RunwayBombingTracker
|
||||
---@field private runwayStrikeMissions table<string, RunwayStrikeMission>
|
||||
local CapBase = {}
|
||||
|
||||
|
||||
---comment
|
||||
---@param self CapBase
|
||||
---@param time number
|
||||
---@return number?
|
||||
local CheckStateContinuous = function(self, time)
|
||||
self:CheckAndScheduleCAP()
|
||||
self:CheckAndScheduleSweep()
|
||||
self:CheckAndScheduleIntercept()
|
||||
return time + 15
|
||||
end
|
||||
|
||||
@@ -22,8 +31,9 @@ end
|
||||
---@param capConfig table
|
||||
---@param stageConfig table
|
||||
---@param runwayBombingTracker RunwayBombingTracker
|
||||
---@param detectionManager DetectionManager
|
||||
---@return CapBase
|
||||
function CapBase.new(airbaseName, database, logger, capConfig, stageConfig, runwayBombingTracker)
|
||||
function CapBase.new(airbaseName, database, logger, capConfig, stageConfig, runwayBombingTracker, detectionManager)
|
||||
CapBase.__index = CapBase
|
||||
local self = setmetatable({}, { __index = CapBase }) --[[@as CapBase]]
|
||||
|
||||
@@ -36,6 +46,9 @@ function CapBase.new(airbaseName, database, logger, capConfig, stageConfig, runw
|
||||
self.capConfig = capConfig
|
||||
self.database = database
|
||||
self.capGroupsByName = {}
|
||||
self.sweepGroupsByName = {}
|
||||
self.interceptGroupsByName = {}
|
||||
self.detectionManager = detectionManager
|
||||
|
||||
local baseData = database:getAirbaseDataForZone(airbaseName)
|
||||
if baseData and baseData.CapGroups then
|
||||
@@ -47,14 +60,36 @@ function CapBase.new(airbaseName, database, logger, capConfig, stageConfig, runw
|
||||
end
|
||||
end
|
||||
|
||||
logger:info("Airbase with name '" ..
|
||||
airbaseName .. "' has a total of " .. Spearhead.Util.tableLength(self.capGroupsByName) .. " cap flights registered")
|
||||
if baseData and baseData.SweepGroups then
|
||||
for key, name in pairs(baseData.SweepGroups) do
|
||||
local sweepGroup = Spearhead.classes.capClasses.airGroups.SweepGroup.New(name, capConfig, logger)
|
||||
if sweepGroup then
|
||||
self.sweepGroupsByName[name] = sweepGroup
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if baseData and baseData.InterceptGroups then
|
||||
for key, name in pairs(baseData.InterceptGroups) do
|
||||
local interceptGroup = Spearhead.classes.capClasses.airGroups.InterceptGroup.New(name, capConfig, logger, detectionManager)
|
||||
if interceptGroup then
|
||||
self.interceptGroupsByName[name] = interceptGroup
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local capFlights = Spearhead.Util.tableLength(self.capGroupsByName)
|
||||
local sweepFlights = Spearhead.Util.tableLength(self.sweepGroupsByName)
|
||||
local interceptFlights = Spearhead.Util.tableLength(self.interceptGroupsByName)
|
||||
|
||||
logger:info(airbaseName .. " : " .. capFlights .." CAP | " .. sweepFlights .. " SWEEP | " .. interceptFlights .. " INTERCEPT")
|
||||
|
||||
self:CreateRunwayStrikeMission(database)
|
||||
|
||||
Spearhead.Events.AddStageNumberChangedListener(self)
|
||||
|
||||
timer.scheduleFunction(CheckStateContinuous, self, timer.getTime() + 15)
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
@@ -91,6 +126,22 @@ function CapBase:SpawnIfApplicable()
|
||||
capGroup:Spawn()
|
||||
end
|
||||
end
|
||||
|
||||
for groupName, sweepGroup in pairs(self.sweepGroupsByName) do
|
||||
local targetStage = sweepGroup:GetZoneIDWhenStageID(tostring(self.activeStage))
|
||||
|
||||
if targetStage ~= nil and sweepGroup:GetState() == "UnSpawned" then
|
||||
sweepGroup:Spawn()
|
||||
end
|
||||
end
|
||||
|
||||
for groupName, interceptGroup in pairs(self.interceptGroupsByName) do
|
||||
local targetStage = interceptGroup:GetZoneIDWhenStageID(tostring(self.activeStage))
|
||||
|
||||
if targetStage ~= nil and interceptGroup:GetState() == "UnSpawned" then
|
||||
interceptGroup:Spawn()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function CapBase:CheckAndScheduleCAP()
|
||||
@@ -223,6 +274,116 @@ function CapBase:CheckAndScheduleCAP()
|
||||
end
|
||||
end
|
||||
|
||||
function CapBase:CheckAndScheduleSweep()
|
||||
self.logger:debug("Check sweep taskings for airbase " .. self.airbaseName)
|
||||
|
||||
local airbase = Airbase.getByName(self.airbaseName)
|
||||
if not airbase then
|
||||
return nil
|
||||
end
|
||||
|
||||
local activeStageID = tostring(self.activeStage)
|
||||
|
||||
for _, group in pairs(self.sweepGroupsByName) do
|
||||
local targetZoneID = group:GetZoneIDWhenStageID(activeStageID)
|
||||
if targetZoneID then
|
||||
local triggerZone = self.database:GetCapZoneForZoneID(targetZoneID)
|
||||
if triggerZone then
|
||||
if group:GetState() == "ReadyOnTheRamp" then
|
||||
group:SendToZone(triggerZone, targetZoneID, airbase)
|
||||
end
|
||||
else
|
||||
self.logger:debug("SweepGroup " .. group:GetName() .. " has no trigger zone for stage " .. activeStageID)
|
||||
group:SendRTB(airbase)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function CapBase:CheckAndScheduleIntercept()
|
||||
|
||||
self.logger:debug("Check intercept taskings for airbase " .. self.airbaseName)
|
||||
|
||||
local interceptZoneIDs = {}
|
||||
|
||||
local airbase = Airbase.getByName(self.airbaseName)
|
||||
if not airbase then
|
||||
return nil
|
||||
end
|
||||
|
||||
for name, group in pairs(self.interceptGroupsByName) do
|
||||
local targetZoneID = group:GetZoneIDWhenStageID(tostring(self.activeStage))
|
||||
if targetZoneID then
|
||||
interceptZoneIDs[targetZoneID] = true
|
||||
end
|
||||
end
|
||||
|
||||
---@type table<string, Array<string>>
|
||||
local unitsToInterceptPerZone = {}
|
||||
|
||||
local detectedUnits = self.detectionManager:GetDetectedUnitsBy(coalition.side.RED)
|
||||
for targetZoneID, _ in pairs(interceptZoneIDs) do
|
||||
local zones = self.database:GetInterceptZonesForZoneID(targetZoneID)
|
||||
if zones then
|
||||
for _, zone in pairs(zones) do
|
||||
|
||||
self.logger:debug("Check intercept zone " .. zone.name .. " for airbase " .. self.airbaseName)
|
||||
|
||||
for _, unitName in pairs(detectedUnits) do
|
||||
self.logger:debug("Check unit " .. unitName .. " for intercept in zone " .. zone.name)
|
||||
|
||||
local unit = Unit.getByName(unitName)
|
||||
if unit and unit:isExist() then
|
||||
local unitPos = unit:getPoint()
|
||||
if Spearhead.Util.is3dPointInZone(unitPos, zone) == true then
|
||||
if not unitsToInterceptPerZone[zone.name] then
|
||||
unitsToInterceptPerZone[zone.name] = {}
|
||||
end
|
||||
|
||||
table.insert(unitsToInterceptPerZone[zone.name], unitName)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---RATIO. Amount of enemy fighters required per zone before another group gets added.
|
||||
local ratio = 4 -- Ratio of units to intercept per zone, can be adjusted
|
||||
|
||||
for name, targets in pairs(unitsToInterceptPerZone) do
|
||||
local required = 0
|
||||
local nrTargets = Spearhead.Util.tableLength(targets)
|
||||
if targets and nrTargets > 0 then
|
||||
required = math.ceil(nrTargets / ratio)
|
||||
end
|
||||
|
||||
local total = 0
|
||||
for _, group in pairs(self.interceptGroupsByName) do
|
||||
if group:GetState() == "OnStation" and group:GetCurrentTargetZone() == name then
|
||||
group:SetTargetUnits(targets)
|
||||
total = total + 1
|
||||
end
|
||||
end
|
||||
|
||||
if total < required then
|
||||
for _, group in pairs(self.interceptGroupsByName) do
|
||||
if total < required then
|
||||
local zoneID = group:GetZoneIDWhenStageID(tostring(self.activeStage))
|
||||
if group:GetState() == "ReadyOnTheRamp" then
|
||||
group:SendToInterceptUnits(targets, name, airbase)
|
||||
total = total + 1
|
||||
|
||||
self.logger:debug("Intercept group " .. group:GetName() .. " sent to intercept zone " .. name )
|
||||
elseif group:GetState() == "InTransit" or group:GetState() == "OnStation" then
|
||||
group:SetTargetUnits(targets)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
function CapBase:OnStageNumberChanged(number)
|
||||
self.activeStage = number
|
||||
|
||||
@@ -243,6 +404,20 @@ function CapBase:IsBaseActiveWhenStageIsActive(stageNumber)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
for _, group in pairs(self.sweepGroupsByName) do
|
||||
local target = group:GetZoneIDWhenStageID(tostring(stageNumber))
|
||||
if target ~= nil then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
for _, group in pairs(self.interceptGroupsByName) do
|
||||
local target = group:GetZoneIDWhenStageID(tostring(stageNumber))
|
||||
if target ~= nil then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
@@ -11,8 +11,9 @@ do
|
||||
---@param database Database
|
||||
---@param capConfig table
|
||||
---@param stageConfig StageConfig
|
||||
---@param detectionManager DetectionManager
|
||||
---@param logLevel LogLevel
|
||||
function GlobalCapManager.start(database, capConfig, stageConfig, logLevel)
|
||||
function GlobalCapManager.start(database, capConfig, detectionManager, stageConfig, logLevel)
|
||||
if initiated == true then return end
|
||||
|
||||
local logger = Spearhead.LoggerTemplate.new("AirbaseManager", logLevel)
|
||||
@@ -31,7 +32,7 @@ do
|
||||
for _, airbaseName in pairs(airbaseNames) do
|
||||
if airbaseName then
|
||||
local airbaseSpecificLogger = Spearhead.LoggerTemplate.new("CAP_" .. airbaseName, logLevel)
|
||||
local airbase = Spearhead.classes.capClasses.CapAirbase.new(airbaseName, database, airbaseSpecificLogger, capConfig, stageConfig, runwayBombingTracker)
|
||||
local airbase = Spearhead.classes.capClasses.CapAirbase.new(airbaseName, database, airbaseSpecificLogger, capConfig, stageConfig, runwayBombingTracker, detectionManager)
|
||||
if airbase then
|
||||
table.insert(airbasesPerStage[stageName], airbase)
|
||||
allAirbasesByName[airbaseName] = airbase
|
||||
|
||||
@@ -113,6 +113,19 @@ function AirGroup:Spawn()
|
||||
self:SpawnInternal(false)
|
||||
end
|
||||
|
||||
function AirGroup:IsInAir()
|
||||
local group = Group.getByName(self._groupName)
|
||||
if group then
|
||||
local units = group:getUnits()
|
||||
for _, unit in pairs(units) do
|
||||
if unit:inAir() == true then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
---@param force boolean
|
||||
---@param withoutLoadout boolean?
|
||||
---@protected
|
||||
@@ -423,6 +436,8 @@ Spearhead.classes.capClasses.airGroups.AirGroup = AirGroup
|
||||
|
||||
---@alias AirGroupType
|
||||
---| "CAP"
|
||||
---| "SWEEP"
|
||||
---| "INTERCEPT"
|
||||
|
||||
---| "CAS"
|
||||
---| "SEAD"
|
||||
|
||||
@@ -80,63 +80,62 @@ 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 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 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])
|
||||
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 Spearhead.Util.strContains(targetZone, "A") == true then
|
||||
self._targetZoneIdPerStage[tostring(i)] = string.gsub(targetZone, "A", tostring(i))
|
||||
else
|
||||
self._targetZoneIdPerStage[tostring(i)] = targetZone
|
||||
end
|
||||
end
|
||||
else
|
||||
for i = from, till do
|
||||
if Spearhead.Util.strContains(targetZone, "A") == true then
|
||||
self._targetZoneIdPerStage[tostring(dashSeperated[1])] = string.gsub(targetZone, "A", tostring(dashSeperated[1]))
|
||||
self._targetZoneIdPerStage[tostring(i)] = string.gsub(targetZone, "A", tostring(i))
|
||||
else
|
||||
self._targetZoneIdPerStage[tostring(dashSeperated[1])] = targetZone
|
||||
self._targetZoneIdPerStage[tostring(i)] = targetZone
|
||||
end
|
||||
end
|
||||
else
|
||||
if Spearhead.Util.strContains(targetZone, "A") == true then
|
||||
self._targetZoneIdPerStage[tostring(dashSeperated[1])] = string.gsub(targetZone, "A", tostring(dashSeperated[1]))
|
||||
else
|
||||
self._targetZoneIdPerStage[tostring(dashSeperated[1])] = targetZone
|
||||
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
|
||||
|
||||
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)
|
||||
end
|
||||
end
|
||||
|
||||
if not Spearhead then Spearhead = {} end
|
||||
|
||||
@@ -0,0 +1,358 @@
|
||||
---@class InterceptGroup : AirGroup
|
||||
---@field private _targetNames Array<string>
|
||||
---@field private _targetZoneName string
|
||||
---@field private _detectionManager DetectionManager
|
||||
---@field private _coalitionSide CoalitionSide
|
||||
---@field private _lastKnownTargetAt number
|
||||
---@field private _currentTargetName string?
|
||||
---@field private _targetZoneIdPerStage table<string, string>
|
||||
---@field private _config CapConfig
|
||||
---@field private _airbase Airbase
|
||||
---@field private _maxSpeed number?
|
||||
---@field private _updateTaskID number|nil
|
||||
local InterceptGroup = {}
|
||||
InterceptGroup.__index = InterceptGroup
|
||||
|
||||
|
||||
---@param groupName string
|
||||
---@param config CapConfig
|
||||
---@param logger Logger
|
||||
---@param detectionManager DetectionManager
|
||||
---@return InterceptGroup?
|
||||
function InterceptGroup.New(groupName, config, logger, detectionManager)
|
||||
|
||||
setmetatable(InterceptGroup, Spearhead.classes.capClasses.airGroups.AirGroup)
|
||||
local self = setmetatable({}, InterceptGroup)
|
||||
Spearhead.classes.capClasses.airGroups.AirGroup.New(self, groupName, "INTERCEPT", config, logger)
|
||||
|
||||
local group = Group.getByName(groupName)
|
||||
if not group then
|
||||
logger:error("InterceptGroup: Group " .. groupName .. " does not exist")
|
||||
return nil
|
||||
end
|
||||
|
||||
local unit = group:getUnit(1)
|
||||
if unit then
|
||||
local desc = unit:getDesc()
|
||||
if desc["speedMax10K"] then
|
||||
self._maxSpeed = desc["speedMax10K"] * 0.75
|
||||
self._logger:debug("InterceptGroup: Max speed for group " .. groupName .. " is set to " .. self._maxSpeed .. " m/s")
|
||||
end
|
||||
end
|
||||
|
||||
self._coalitionSide = group:getCoalition()
|
||||
self._detectionManager = detectionManager
|
||||
self._config = config
|
||||
self._targetNames = {}
|
||||
self._targetZoneIdPerStage = {}
|
||||
|
||||
self:InitWithName(groupName)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
---comment
|
||||
---@param units Array<string>
|
||||
---@param homeAirbase Airbase
|
||||
function InterceptGroup:SendToInterceptUnits(units, zoneName, homeAirbase)
|
||||
|
||||
self._airbase = homeAirbase
|
||||
self._targetZoneName = zoneName
|
||||
self:SetTargetUnits(units)
|
||||
end
|
||||
|
||||
---@return string?
|
||||
function InterceptGroup:GetZoneIDWhenStageID(stageID)
|
||||
return self._targetZoneIdPerStage[stageID]
|
||||
end
|
||||
|
||||
---@return string?
|
||||
function InterceptGroup:GetCurrentTargetZone()
|
||||
return self._targetZoneName
|
||||
end
|
||||
|
||||
---comment
|
||||
---@param unitNames Array<string>
|
||||
function InterceptGroup:SetTargetUnits(unitNames)
|
||||
|
||||
self._targetNames = unitNames
|
||||
self:UpdateTask()
|
||||
|
||||
if self._updateTaskID then
|
||||
timer.removeFunction(self._updateTaskID)
|
||||
end
|
||||
|
||||
local updateContinous = function(selfA, time)
|
||||
local next = selfA:UpdateTask()
|
||||
if next then
|
||||
return time + next
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
self._updateTaskID = timer.scheduleFunction(updateContinous, self, timer.getTime() + 30)
|
||||
end
|
||||
|
||||
function InterceptGroup:RemoveTargetUnit(unit)
|
||||
|
||||
if not unit then return end
|
||||
|
||||
local name = unit:getName()
|
||||
for key, value in pairs(self._targetNames) do
|
||||
if value == "name" then
|
||||
table.remove(self._targetNames, key)
|
||||
self._logger:debug("InterceptGroup: Removed target unit " .. name .. " from group " .. self._groupName)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@private
|
||||
---@return Unit?
|
||||
---@return Vec3? groupPoint
|
||||
function InterceptGroup:GetClosestTarget()
|
||||
|
||||
local group = Group.getByName(self._groupName)
|
||||
if not group then return nil end
|
||||
|
||||
local groupPoint = nil
|
||||
for _, unit in pairs(group:getUnits()) do
|
||||
if unit and unit:isExist() then
|
||||
groupPoint = unit:getPoint()
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not groupPoint then return nil end
|
||||
|
||||
local closestUnit = nil
|
||||
local closestDistance = math.huge
|
||||
|
||||
for _, targetName in pairs(self._targetNames) do
|
||||
local targetUnit = Unit.getByName(targetName)
|
||||
if targetUnit and targetUnit:isExist() then
|
||||
local pos = targetUnit:getPoint()
|
||||
local distance = Spearhead.Util.VectorDistance3d(groupPoint, pos)
|
||||
if distance < closestDistance then
|
||||
closestDistance = distance
|
||||
closestUnit = targetUnit
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return closestUnit, groupPoint
|
||||
end
|
||||
|
||||
---@return number? @interval or null if no retry required
|
||||
function InterceptGroup:UpdateTask()
|
||||
local group = Group.getByName(self._groupName)
|
||||
if not group then return end
|
||||
|
||||
|
||||
local closestUnit, groupPoint = self:GetClosestTarget()
|
||||
if not closestUnit then return 15 end
|
||||
if not groupPoint then return 15 end
|
||||
|
||||
local closesUnitVec = closestUnit:getPoint()
|
||||
local alt = closesUnitVec.y
|
||||
if alt < 1000 then
|
||||
alt = 1000 -- Ensure minimum altitude for intercept
|
||||
end
|
||||
|
||||
local selfDetected = false
|
||||
for _, unit in pairs(group:getUnits()) do
|
||||
if unit and unit:isExist() then
|
||||
local controller = unit:getController()
|
||||
if controller then
|
||||
for _, detected in pairs(controller:getDetectedTargets(Controller.Detection.VISUAL, Controller.Detection.OPTIC, Controller.Detection.RADAR)) do
|
||||
if detected and detected.object and detected.distance == true then
|
||||
if detected.object:getName() == closestUnit:getName() then
|
||||
selfDetected = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if Spearhead.DcsUtil.IsBingoFuel(self._groupName) then
|
||||
self._logger:debug("InterceptGroup: " .. self._groupName .. " is at bingo fuel, returning to base")
|
||||
self:SendRTB(self._airbase)
|
||||
return nil -- Return to base if bingo fuel
|
||||
end
|
||||
|
||||
if selfDetected and self:IsInAir() == true then
|
||||
if self._currentTargetName and self._currentTargetName == closestUnit:getName() then
|
||||
local distance = Spearhead.Util.VectorDistance3d(closestUnit:getPoint(), groupPoint)
|
||||
if distance < 30 * 1852 then
|
||||
-- If the target is within 10 nautical miles, continue attacking
|
||||
self._logger:debug("InterceptGroup: " .. self._groupName .. " continues attacking target " .. closestUnit:getName())
|
||||
return 15 -- Continue attacking the detected target
|
||||
end
|
||||
end
|
||||
|
||||
self._logger:debug("InterceptGroup: " .. self._groupName .. " has detected target " .. closestUnit:getName() .. ", creating intercept mission")
|
||||
local vec3 = closestUnit:getPoint()
|
||||
local vec2 = { x = vec3.x, y = vec3.z }
|
||||
local groupPointVec2 = { x = groupPoint.x, y = groupPoint.z }
|
||||
local mission = Spearhead.classes.capClasses.taskings.INTERCEPT.getUnitInterceptMissionFromAir(self._groupName, groupPointVec2, vec2, closestUnit, self._airbase, self._config, self._maxSpeed, alt)
|
||||
self:SetMission(mission)
|
||||
self._currentTargetName = closestUnit:getName()
|
||||
return 15 -- Just continue attacking the detected target
|
||||
else
|
||||
self._currentTargetName = nil
|
||||
end
|
||||
|
||||
|
||||
|
||||
local speed = self._config:getMaxSpeed()
|
||||
local interceptPoint = self:GetInterceptPoint(groupPoint, speed, closestUnit)
|
||||
|
||||
if interceptPoint == nil then
|
||||
return 30 -- Return to base if no intercept point could be calculated
|
||||
end
|
||||
|
||||
local mission = nil
|
||||
if self:IsInAir() == true then
|
||||
-- If the group is in the air, create an intercept mission
|
||||
mission = Spearhead.classes.capClasses.taskings.INTERCEPT.getMissionFromInAir(
|
||||
self._groupName,
|
||||
{ x = groupPoint.x, y = groupPoint.z },
|
||||
interceptPoint,
|
||||
self._airbase,
|
||||
self._config,
|
||||
self._maxSpeed,
|
||||
alt
|
||||
)
|
||||
else
|
||||
mission = Spearhead.classes.capClasses.taskings.INTERCEPT.getMissionFromAirbase(
|
||||
self._groupName,
|
||||
interceptPoint,
|
||||
self._airbase,
|
||||
self._config,
|
||||
self._maxSpeed,
|
||||
alt
|
||||
)
|
||||
end
|
||||
|
||||
if mission then
|
||||
self:SetMission(mission)
|
||||
else
|
||||
self._logger:warn("InterceptGroup: Could not create mission for group " .. self._groupName)
|
||||
return 30 -- Return to base if mission could not be created
|
||||
end
|
||||
|
||||
return 15
|
||||
end
|
||||
|
||||
|
||||
---@param originatingUnit Vec3
|
||||
---@param speed number
|
||||
---@param targetUnit Unit
|
||||
---@return Vec2? @Returns the intercept point as a Vec2 or nil if no intercept point could be calculated
|
||||
function InterceptGroup:GetInterceptPoint(originatingUnit, speed, targetUnit)
|
||||
|
||||
-- Calculate intercept point for a moving target
|
||||
-- originatingUnit: Vec3 (our position)
|
||||
-- speed: our speed (scalar, m/s)
|
||||
-- targetUnit: Unit (target)
|
||||
|
||||
if not targetUnit or not targetUnit:isExist() then return nil end
|
||||
|
||||
local targetPos = targetUnit:getPoint()
|
||||
local targetVel = targetUnit:getVelocity() -- Vec3
|
||||
local relPos = {
|
||||
x = targetPos.x - originatingUnit.x,
|
||||
y = targetPos.y - originatingUnit.y,
|
||||
z = targetPos.z - originatingUnit.z
|
||||
}
|
||||
local relVel = {
|
||||
x = targetVel.x,
|
||||
y = targetVel.y,
|
||||
z = targetVel.z
|
||||
}
|
||||
local relPos2 = relPos.x^2 + relPos.y^2 + relPos.z^2
|
||||
local relVel2 = relVel.x^2 + relVel.y^2 + relVel.z^2
|
||||
local speed2 = speed^2
|
||||
local dot = relPos.x * relVel.x + relPos.y * relVel.y + relPos.z * relVel.z
|
||||
|
||||
-- Quadratic formula: a*t^2 + b*t + c = 0
|
||||
local a = relVel2 - speed2
|
||||
local b = 2 * dot
|
||||
local c = relPos2
|
||||
local discriminant = b^2 - 4*a*c
|
||||
if discriminant < 0 or a == 0 then
|
||||
-- No solution, just head to current position
|
||||
return { x = targetPos.x, y = targetPos.z }
|
||||
end
|
||||
local sqrtDisc = math.sqrt(discriminant)
|
||||
local t1 = (-b + sqrtDisc) / (2*a)
|
||||
local t2 = (-b - sqrtDisc) / (2*a)
|
||||
local t = math.min(t1, t2)
|
||||
if t < 0 then t = math.max(t1, t2) end
|
||||
if t < 0 then
|
||||
-- No valid intercept time, just head to current position
|
||||
return { x = targetPos.x, y = targetPos.z }
|
||||
end
|
||||
-- Intercept point
|
||||
local intercept = {
|
||||
x = targetPos.x + targetVel.x * t,
|
||||
y = targetPos.z + targetVel.z * t
|
||||
}
|
||||
return intercept
|
||||
end
|
||||
|
||||
|
||||
|
||||
---@private
|
||||
function InterceptGroup: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]
|
||||
configPart = string.sub(configPart, 2, #configPart)
|
||||
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 Spearhead.Util.strContains(targetZone, "A") == true then
|
||||
self._targetZoneIdPerStage[tostring(i)] = string.gsub(targetZone, "A", tostring(i))
|
||||
else
|
||||
self._targetZoneIdPerStage[tostring(i)] = targetZone
|
||||
end
|
||||
end
|
||||
else
|
||||
if Spearhead.Util.strContains(targetZone, "A") == true then
|
||||
self._targetZoneIdPerStage[tostring(dashSeperated[1])] = string.gsub(targetZone, "A", tostring(dashSeperated[1]))
|
||||
else
|
||||
self._targetZoneIdPerStage[tostring(dashSeperated[1])] = targetZone
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
env.info("interceptGroup 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)
|
||||
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.InterceptGroup = InterceptGroup
|
||||
@@ -0,0 +1,120 @@
|
||||
---@class SweepGroup : AirGroup
|
||||
---@field _targetZoneIdPerStage table<number, string>
|
||||
---@field _currentTargetZoneID string?
|
||||
local SweepGroup = {}
|
||||
SweepGroup.__index = SweepGroup
|
||||
|
||||
|
||||
---@param groupName string
|
||||
---@param config CapConfig
|
||||
---@param logger Logger
|
||||
---@return SweepGroup
|
||||
function SweepGroup.New(groupName, config, logger)
|
||||
setmetatable(SweepGroup, Spearhead.classes.capClasses.airGroups.AirGroup)
|
||||
local self = setmetatable({}, SweepGroup) --[[@as SweepGroup]]
|
||||
Spearhead.classes.capClasses.airGroups.AirGroup.New(self, groupName, "SWEEP", config, logger)
|
||||
|
||||
self._targetZoneIdPerStage = {}
|
||||
self:InitWithName(groupName)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
---@return string?
|
||||
function SweepGroup:GetZoneIDWhenStageID(stageID)
|
||||
return self._targetZoneIdPerStage[stageID]
|
||||
end
|
||||
|
||||
---@return string?
|
||||
function SweepGroup:GetCurrentTargetZoneID()
|
||||
return self._currentTargetZoneID
|
||||
end
|
||||
|
||||
---@class SetTaskParams
|
||||
---@field task table
|
||||
---@field self SweepGroup
|
||||
|
||||
---@param params SetTaskParams
|
||||
local setMissionDelayedTask = function(params, time)
|
||||
params.self:SetMissionPrivate(params.task)
|
||||
end
|
||||
|
||||
function SweepGroup: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 mission = Spearhead.classes.capClasses.taskings.SWEEP.getAsMissionFromAirbase(self._groupName, airbase, zone, self._config)
|
||||
if mission then
|
||||
---@type SetTaskParams
|
||||
local params = {
|
||||
task = mission,
|
||||
self = self
|
||||
}
|
||||
local delay = math.random(120, 600)
|
||||
timer.scheduleFunction(setMissionDelayedTask, params, timer.getTime() + delay)
|
||||
else
|
||||
self._logger:error("SweepGroup:SendToZone - Mission could not be created for group: " .. self._groupName)
|
||||
end
|
||||
end
|
||||
|
||||
function SweepGroup:SendToZoneInternal()
|
||||
|
||||
|
||||
end
|
||||
|
||||
---@private
|
||||
function SweepGroup: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]
|
||||
configPart = string.sub(configPart, 2, #configPart)
|
||||
|
||||
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 Spearhead.Util.strContains(targetZone, "A") == true then
|
||||
self._targetZoneIdPerStage[tostring(i)] = string.gsub(targetZone, "A", tostring(i))
|
||||
else
|
||||
self._targetZoneIdPerStage[tostring(i)] = targetZone
|
||||
end
|
||||
end
|
||||
else
|
||||
if Spearhead.Util.strContains(targetZone, "A") == true then
|
||||
self._targetZoneIdPerStage[tostring(dashSeperated[1])] = string.gsub(targetZone, "A",
|
||||
tostring(dashSeperated[1]))
|
||||
else
|
||||
self._targetZoneIdPerStage[tostring(dashSeperated[1])] = targetZone
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
env.info("SweepGroup parsed with table: " .. Spearhead.Util.toString(self._targetZoneIdPerStage))
|
||||
else
|
||||
Spearhead.AddMissionEditorWarning("SWEEP Group with name: " ..
|
||||
groupName .. "should have at least 3 parts, but has " .. partCount)
|
||||
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.SweepGroup = SweepGroup
|
||||
@@ -0,0 +1,168 @@
|
||||
|
||||
---@class DetectionManager
|
||||
---@field private _logger Logger
|
||||
---@field private _detectedUnits table<string, table<string, number>>
|
||||
---@field private _detectingUnits table<string, table<string, Unit>>
|
||||
local DetectionManager = {}
|
||||
DetectionManager.__index = DetectionManager
|
||||
|
||||
|
||||
---@param logger Logger
|
||||
function DetectionManager.New(logger)
|
||||
local self = setmetatable({}, DetectionManager)
|
||||
|
||||
self._logger = logger
|
||||
self._detectedUnits = {
|
||||
[tostring(coalition.side.RED)] = {},
|
||||
[tostring(coalition.side.BLUE)] = {}
|
||||
}
|
||||
|
||||
self._detectingUnits = {
|
||||
[tostring(coalition.side.RED)] = {},
|
||||
[tostring(coalition.side.BLUE)] = {}
|
||||
}
|
||||
|
||||
---@param selfA DetectionManager
|
||||
---@param time number
|
||||
local updateDetectingUnitsTask = function(selfA, time)
|
||||
selfA:UpdateDetectingUnits()
|
||||
return time + 120
|
||||
end
|
||||
timer.scheduleFunction(updateDetectingUnitsTask, self, timer.getTime() + 120)
|
||||
|
||||
---@param selfA DetectionManager
|
||||
---@param time number
|
||||
local updateDetected = function(selfA, time)
|
||||
selfA:UpdateDetectedUnits()
|
||||
return time + 10
|
||||
end
|
||||
timer.scheduleFunction(updateDetected, self, timer.getTime() + 130)
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
---@param unitName string
|
||||
---@param coalitionSide CoalitionSide
|
||||
function DetectionManager:IsUnitDetectedBy(unitName, coalitionSide)
|
||||
local coalitionString = tostring(coalitionSide)
|
||||
if not self._detectedUnits[coalitionString] then
|
||||
return false
|
||||
end
|
||||
|
||||
if not self._detectedUnits[coalitionString][unitName] then
|
||||
return false
|
||||
end
|
||||
|
||||
return timer.getTime() - self._detectedUnits[coalitionString][unitName] < 20
|
||||
end
|
||||
|
||||
---@return Array<string>
|
||||
function DetectionManager:GetDetectedUnitsBy(coalitionSide)
|
||||
local coalitionString = tostring(coalitionSide)
|
||||
if not self._detectedUnits[coalitionString] then
|
||||
return {}
|
||||
end
|
||||
|
||||
local detectedUnits = {}
|
||||
for unitName, _ in pairs(self._detectedUnits[coalitionString]) do
|
||||
if self:IsUnitDetectedBy(unitName, coalitionSide) then
|
||||
table.insert(detectedUnits, unitName)
|
||||
end
|
||||
end
|
||||
|
||||
return detectedUnits
|
||||
end
|
||||
|
||||
function DetectionManager:UpdateDetectingUnits()
|
||||
self:UpdateDetectingInCoalition(coalition.side.RED)
|
||||
self:UpdateDetectingInCoalition(coalition.side.BLUE)
|
||||
self._logger:debug("Updated detecting units")
|
||||
end
|
||||
|
||||
function DetectionManager:UpdateDetectedUnits()
|
||||
self:UpdateDetectedUnitsByCoalition(coalition.side.RED)
|
||||
self:UpdateDetectedUnitsByCoalition(coalition.side.BLUE)
|
||||
self._logger:debug("Updated detected units")
|
||||
end
|
||||
|
||||
---@private
|
||||
---@param unit Unit
|
||||
---@return boolean
|
||||
function DetectionManager:IsDetectingType(unit)
|
||||
return unit:hasAttribute("EWR") or unit:hasAttribute("AWACS") or unit:hasAttribute("SAM SR")
|
||||
end
|
||||
|
||||
---@private
|
||||
---@param coalitionSide CoalitionSide
|
||||
function DetectionManager:UpdateDetectingInCoalition(coalitionSide)
|
||||
|
||||
local coalitionString = tostring(coalitionSide)
|
||||
|
||||
local airGroups = coalition.getGroups(coalitionSide, Group.Category.AIRPLANE)
|
||||
for _, group in ipairs(airGroups) do
|
||||
if group and group:isExist() then
|
||||
for _, unit in ipairs(group:getUnits()) do
|
||||
if unit and self:IsDetectingType(unit) == true then
|
||||
self._detectingUnits[coalitionString][unit:getName()] = unit
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local groundGroups = coalition.getGroups(coalitionSide, Group.Category.GROUND)
|
||||
for _, group in ipairs(groundGroups) do
|
||||
if group and group:isExist() then
|
||||
for _, unit in ipairs(group:getUnits()) do
|
||||
if unit and self:IsDetectingType(unit) == true then
|
||||
self._detectingUnits[coalitionString][unit:getName()] = unit
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local ships = coalition.getGroups(coalitionSide, Group.Category.SHIP)
|
||||
for _, group in ipairs(ships) do
|
||||
if group and group:isExist() then
|
||||
for _, unit in ipairs(group:getUnits()) do
|
||||
if unit and self:IsDetectingType(unit) == true then
|
||||
self._detectingUnits[coalitionString][unit:getName()] = unit
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
---@private
|
||||
---@param coalitionSide CoalitionSide
|
||||
function DetectionManager:UpdateDetectedUnitsByCoalition(coalitionSide)
|
||||
|
||||
local coalitionString = tostring(coalitionSide)
|
||||
if not self._detectingUnits[coalitionString] then return end
|
||||
if not self._detectedUnits[coalitionString] then
|
||||
self._detectedUnits[coalitionString] = {}
|
||||
end
|
||||
|
||||
for _, detectingUnit in pairs(self._detectingUnits[coalitionString]) do
|
||||
if detectingUnit and detectingUnit:isExist() then
|
||||
local controller = detectingUnit:getController()
|
||||
if controller then
|
||||
local targets = controller:getDetectedTargets(Controller.Detection.RADAR)
|
||||
for _, target in pairs(targets) do
|
||||
if target and target.object ~= nil and target.distance == true then
|
||||
local targetUnit = target.object
|
||||
local name = targetUnit:getName()
|
||||
self._detectedUnits[coalitionString][name] = timer.getTime()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
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.detection then Spearhead.classes.capClasses.detection = {} end
|
||||
Spearhead.classes.capClasses.detection.DetectionManager = DetectionManager
|
||||
@@ -0,0 +1,399 @@
|
||||
|
||||
|
||||
---@class InterceptTasking
|
||||
local INTERCEPT = {}
|
||||
|
||||
|
||||
---@param groupName string
|
||||
---@param interceptPoint Vec2
|
||||
---@param airbase Airbase
|
||||
---@param speed number?
|
||||
---@param alt number?
|
||||
---@param config CapConfig
|
||||
function INTERCEPT.getMissionFromAirbase(groupName, interceptPoint, airbase, config, speed, alt)
|
||||
|
||||
local airbaseVec3 = airbase:getPoint()
|
||||
local airbaseVec2 = { x = airbaseVec3.x, y = airbaseVec3.z }
|
||||
local heading = Spearhead.Util.vectorHeadingFromTo(airbaseVec2, interceptPoint)
|
||||
|
||||
env.info("BLAAT: heading from runway to first point: " .. heading)
|
||||
|
||||
local point = Spearhead.Util.vectorMove(airbaseVec2, heading, 3*1852)
|
||||
|
||||
local pointA, pointB, pointC, pointD = INTERCEPT.getInterceptTaskPoint(groupName, point, interceptPoint, airbaseVec2, config, speed, alt)
|
||||
|
||||
local points = {
|
||||
[1] = pointA,
|
||||
[2] = pointA,
|
||||
[3] = pointB,
|
||||
[4] = pointC,
|
||||
[5] = pointD,
|
||||
[6] = Spearhead.classes.capClasses.taskings.RTB.getApproachPoint(airbase, interceptPoint, config),
|
||||
[7] = Spearhead.classes.capClasses.taskings.RTB.getInitialPoint(airbase),
|
||||
[8] = Spearhead.classes.capClasses.taskings.RTB.getLandingPoint(airbase)
|
||||
}
|
||||
|
||||
local mission = {
|
||||
id = 'Mission',
|
||||
params = {
|
||||
airborne = true,
|
||||
route = {
|
||||
points = points
|
||||
}
|
||||
}
|
||||
}
|
||||
return mission
|
||||
end
|
||||
|
||||
---comment
|
||||
---@param groupName string
|
||||
---@param currentPosition Vec2
|
||||
---@param interceptPoint Vec2
|
||||
---@param airbase Airbase
|
||||
---@param speed number?
|
||||
---@param alt number?
|
||||
---@param config CapConfig
|
||||
---@return table
|
||||
function INTERCEPT.getMissionFromInAir(groupName, currentPosition, interceptPoint, airbase, config, speed, alt)
|
||||
|
||||
local pointAirbasev3 = airbase:getPoint()
|
||||
local airbase2 = { x = pointAirbasev3.x, y = pointAirbasev3.z }
|
||||
|
||||
local pointA, pointB, pointC, pointD = INTERCEPT.getInterceptTaskPoint(groupName, currentPosition, interceptPoint, airbase2, config, speed, alt)
|
||||
|
||||
local points = {
|
||||
[1] = pointA,
|
||||
[2] = pointA,
|
||||
[3] = pointB,
|
||||
[4] = pointC,
|
||||
[5] = pointD,
|
||||
[6] = Spearhead.classes.capClasses.taskings.RTB.getApproachPoint(airbase, interceptPoint, config),
|
||||
[7] = Spearhead.classes.capClasses.taskings.RTB.getInitialPoint(airbase),
|
||||
[8] = Spearhead.classes.capClasses.taskings.RTB.getLandingPoint(airbase)
|
||||
}
|
||||
|
||||
local mission = {
|
||||
id = 'Mission',
|
||||
params = {
|
||||
airborne = true,
|
||||
route = {
|
||||
points = points
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mission
|
||||
|
||||
end
|
||||
|
||||
---comment
|
||||
---@param groupName string
|
||||
---@param currentPoint Vec2
|
||||
---@param targetPosition Vec2
|
||||
---@param targetUnit Unit
|
||||
---@param airbase any
|
||||
---@param config any
|
||||
---@param speed number?
|
||||
---@param alt number?
|
||||
---@return table
|
||||
function INTERCEPT.getUnitInterceptMissionFromAir(groupName, currentPoint, targetPosition, targetUnit, airbase, config, speed, alt)
|
||||
|
||||
local pointAirbasev3 = airbase:getPoint()
|
||||
local airbase2 = { x = pointAirbasev3.x, y = pointAirbasev3.z }
|
||||
|
||||
local pointA, pointB, pointC, pointD = INTERCEPT.getUnitInterceptTaskPoint(groupName, currentPoint, targetPosition, targetUnit, airbase2, config, speed, alt)
|
||||
|
||||
local points = {
|
||||
[1] = pointA,
|
||||
[2] = pointA,
|
||||
[3] = pointB,
|
||||
[4] = pointC,
|
||||
[5] = pointD,
|
||||
[6] = Spearhead.classes.capClasses.taskings.RTB.getApproachPoint(airbase, currentPoint, config),
|
||||
[7] = Spearhead.classes.capClasses.taskings.RTB.getInitialPoint(airbase),
|
||||
[8] = 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 currentPoint Vec2
|
||||
---@param targetPoint Vec2
|
||||
---@param airbasePoint Vec2
|
||||
---@param config CapConfig
|
||||
---@param speed number?
|
||||
---@param alt number?
|
||||
---@return table pointA @Starts the task right away
|
||||
---@return table pointB @The actual target point to fly to and search, but searching started at pointA
|
||||
---@return table pointC A fly over point halfway between the target and the airbase, if nothing it found then the unit will fly it's route till here
|
||||
---@return table pointD THe point after Point C where the unit will execute the RTB command
|
||||
function INTERCEPT.getInterceptTaskPoint(groupName, currentPoint, targetPoint, airbasePoint, config, speed, alt)
|
||||
|
||||
local pointA = {
|
||||
alt = alt or config:getMinAlt(),
|
||||
action = "Turning Point",
|
||||
alt_type = "BARO",
|
||||
speed = speed or config:getMaxSpeed(),
|
||||
ETA = 0,
|
||||
ETA_locked = false,
|
||||
x = currentPoint.x,
|
||||
y = currentPoint.y,
|
||||
speed_locked = true,
|
||||
formation_template = "",
|
||||
task = {
|
||||
id = "ComboTask",
|
||||
params = {
|
||||
tasks = {
|
||||
id = 'EngageTargetsInZone',
|
||||
params = {
|
||||
point = targetPoint,
|
||||
zoneRadius = 10 * 1852, -- 10 NM, point will be updated, so target should be in this zone.
|
||||
targetTypes = {
|
||||
[1] = "Planes",
|
||||
},
|
||||
priority = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
local pointB = {
|
||||
alt = alt or config:getMinAlt(),
|
||||
action = "Turning Point",
|
||||
alt_type = "BARO",
|
||||
speed = speed or config:getMaxSpeed(),
|
||||
ETA = 0,
|
||||
ETA_locked = false,
|
||||
x = targetPoint.x,
|
||||
y = targetPoint.y,
|
||||
speed_locked = true,
|
||||
formation_template = "",
|
||||
task = {
|
||||
id = "ComboTask",
|
||||
params = {
|
||||
tasks = {
|
||||
id = 'EngageTargetsInZone',
|
||||
params = {
|
||||
point = targetPoint,
|
||||
zoneRadius = 10 * 1852, -- 10 NM, point will be updated, so target should be in this zone.
|
||||
targetTypes = {
|
||||
[1] = "Planes",
|
||||
},
|
||||
priority = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
local midPoint = {
|
||||
x = (targetPoint.x + airbasePoint.x) / 2,
|
||||
y = (targetPoint.y + airbasePoint.y) / 2
|
||||
}
|
||||
|
||||
local pointC = {
|
||||
alt = config:getMinAlt(),
|
||||
action = "Fly Over Point",
|
||||
alt_type = "BARO",
|
||||
speed = config:getMaxSpeed(),
|
||||
ETA = 0,
|
||||
ETA_locked = false,
|
||||
x = midPoint.x,
|
||||
y = midPoint.y,
|
||||
speed_locked = true,
|
||||
formation_template = "",
|
||||
task = {
|
||||
id = "ComboTask",
|
||||
params = {}
|
||||
}
|
||||
}
|
||||
|
||||
local pointD = {
|
||||
alt = config:getMinAlt(),
|
||||
action = "Turning Point",
|
||||
alt_type = "BARO",
|
||||
speed = config:getMaxSpeed(),
|
||||
ETA = 0,
|
||||
ETA_locked = false,
|
||||
x = midPoint.x,
|
||||
y = midPoint.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.PublishRTB, \"" .. groupName .. "\")"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pointA, pointB, pointC, pointD
|
||||
end
|
||||
|
||||
|
||||
---@private
|
||||
---@param groupName string
|
||||
---@param currentPoint Vec2
|
||||
---@param targetPosition Vec2
|
||||
---@param targetUnit Unit
|
||||
---@param airbasePoint Vec2
|
||||
---@param config CapConfig
|
||||
---@param speed number?
|
||||
---@param alt number?
|
||||
---@return table pointA @Starts the task right away
|
||||
---@return table pointB @The actual target point to fly to and search, but searching started at pointA
|
||||
---@return table pointC A fly over point halfway between the target and the airbase, if nothing it found then the unit will fly it's route till here
|
||||
---@return table pointD THe point after Point C where the unit will execute the RTB command
|
||||
function INTERCEPT.getUnitInterceptTaskPoint(groupName, currentPoint, targetPosition, targetUnit, airbasePoint, config, speed, alt)
|
||||
|
||||
local pointA = {
|
||||
alt = alt or config:getMinAlt(),
|
||||
action = "Turning Point",
|
||||
alt_type = "BARO",
|
||||
speed = speed or config:getMaxSpeed(),
|
||||
ETA = 0,
|
||||
ETA_locked = false,
|
||||
x = currentPoint.x,
|
||||
y = currentPoint.y,
|
||||
speed_locked = true,
|
||||
formation_template = "",
|
||||
task = {
|
||||
id = "ComboTask",
|
||||
params = {
|
||||
tasks = {
|
||||
[1] = {
|
||||
id = 'EngageUnit',
|
||||
params = {
|
||||
unitId = targetUnit:getID(),
|
||||
groupAttack = true,
|
||||
priority = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
local pointB = {
|
||||
alt = alt or config:getMinAlt(),
|
||||
action = "Turning Point",
|
||||
alt_type = "BARO",
|
||||
speed = speed or config:getMaxSpeed(),
|
||||
ETA = 0,
|
||||
ETA_locked = false,
|
||||
x = targetPosition.x,
|
||||
y = targetPosition.y,
|
||||
speed_locked = true,
|
||||
formation_template = "",
|
||||
task = {
|
||||
id = "ComboTask",
|
||||
params = {
|
||||
tasks = {
|
||||
[1] = {
|
||||
id = 'EngageUnit',
|
||||
params = {
|
||||
unitId = targetUnit:getID(),
|
||||
groupAttack = true,
|
||||
priority = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
local midPoint = {
|
||||
x = (targetPosition.x + airbasePoint.x) / 2,
|
||||
y = (targetPosition.y + airbasePoint.y) / 2
|
||||
}
|
||||
|
||||
local pointC = {
|
||||
alt = config:getMinAlt(),
|
||||
action = "Fly Over Point",
|
||||
alt_type = "BARO",
|
||||
speed = config:getMaxSpeed(),
|
||||
ETA = 0,
|
||||
ETA_locked = false,
|
||||
x = midPoint.x,
|
||||
y = midPoint.y,
|
||||
speed_locked = true,
|
||||
formation_template = "",
|
||||
task = {
|
||||
id = "ComboTask",
|
||||
params = {}
|
||||
}
|
||||
}
|
||||
|
||||
local pointD = {
|
||||
alt = config:getMinAlt(),
|
||||
action = "Turning Point",
|
||||
alt_type = "BARO",
|
||||
speed = config:getMaxSpeed(),
|
||||
ETA = 0,
|
||||
ETA_locked = false,
|
||||
x = midPoint.x,
|
||||
y = midPoint.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.PublishRTB, \"" .. groupName .. "\")"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pointA, pointB, pointC, pointD
|
||||
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.INTERCEPT = INTERCEPT
|
||||
@@ -0,0 +1,287 @@
|
||||
---@class SWEEPTasking
|
||||
local SWEEP = {}
|
||||
|
||||
---@param attackHelos boolean
|
||||
---@return table
|
||||
local function GetCAPTargetTypes(attackHelos)
|
||||
local targetTypes = {
|
||||
[1] = "Planes",
|
||||
}
|
||||
|
||||
if attackHelos then
|
||||
targetTypes[2] = "Helicopters"
|
||||
end
|
||||
|
||||
return targetTypes
|
||||
end
|
||||
|
||||
---@class SWEEPTaskingOptions
|
||||
---@field furthest Vec2
|
||||
---@field closest Vec2
|
||||
|
||||
---@param capZone SpearheadTriggerZone
|
||||
---@param airBase Airbase
|
||||
---@return SWEEPTaskingOptions
|
||||
local function GetCAPPointFromTriggerZone(airBase, capZone)
|
||||
local furthestA = nil
|
||||
local furthestB = nil
|
||||
|
||||
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(),
|
||||
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 SWEEP.getAsMissionFromAirbase(groupName, airbase, capZone, capConfig)
|
||||
|
||||
local pointA, pointB, pointC = SWEEP.getAsTasking(groupName, airbase, capZone, capConfig)
|
||||
|
||||
local points = {
|
||||
[1] = GetOutboundTask(airbase, capZone, capConfig),
|
||||
[2] = GetOutboundTask(airbase, capZone, capConfig),
|
||||
[3] = pointA,
|
||||
[4] = pointB,
|
||||
[5] = pointC,
|
||||
[6] = Spearhead.classes.capClasses.taskings.RTB.getApproachPoint(airbase, capZone.location, capConfig),
|
||||
[7] = Spearhead.classes.capClasses.taskings.RTB.getInitialPoint(airbase),
|
||||
[8] = 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
|
||||
---@return table TurningPointA
|
||||
---@return table TurningPointB
|
||||
---@return table TurningPointC
|
||||
function SWEEP.getAsTasking(groupName, airbase, capZone, capConfig)
|
||||
|
||||
local capTaskingOptions = GetCAPPointFromTriggerZone(airbase, capZone)
|
||||
|
||||
|
||||
local alt = math.random(capConfig:getMinAlt(), capConfig:getMaxAlt())
|
||||
local speed = capConfig:getMaxSpeed()
|
||||
|
||||
local pointA = {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
local pointB = {
|
||||
alt = alt,
|
||||
action = "Turning Point",
|
||||
alt_type = "BARO",
|
||||
speed = speed,
|
||||
ETA = 0,
|
||||
ETA_locked = false,
|
||||
x = capTaskingOptions.furthest.x,
|
||||
y = capTaskingOptions.furthest.y,
|
||||
speed_locked = true,
|
||||
formation_template = "",
|
||||
task = {
|
||||
id = "ComboTask",
|
||||
params = {
|
||||
tasks = {
|
||||
[1] = {
|
||||
id = 'EngageTargets',
|
||||
params = {
|
||||
maxDist = capConfig:getMaxDeviationRange(),
|
||||
maxDistEnabled = capConfig:getMaxDeviationRange() >= 0, -- required to check maxDist
|
||||
targetTypes = GetCAPTargetTypes(false),
|
||||
priority = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
local pointC = {
|
||||
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] = {
|
||||
id = 'EngageTargets',
|
||||
params = {
|
||||
maxDist = capConfig:getMaxDeviationRange(),
|
||||
maxDistEnabled = capConfig:getMaxDeviationRange() >= 0, -- required to check maxDist
|
||||
targetTypes = GetCAPTargetTypes(false),
|
||||
priority = 0
|
||||
}
|
||||
},
|
||||
[2] = {
|
||||
number = 2,
|
||||
auto = false,
|
||||
id = "WrappedAction",
|
||||
enabled = "true",
|
||||
params = {
|
||||
action = {
|
||||
id = "Script",
|
||||
params = {
|
||||
command = "pcall(Spearhead.Events.PublishRTB, \"" .. groupName .. "\")"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pointA, pointB, pointC
|
||||
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.SWEEP = SWEEP
|
||||
Reference in New Issue
Block a user