Feature/mission refactor (#26)
* Refactored Mission classes to add Missions other than trigger zone missions. (eg. Runway Strikes, Resupply etc.) * Runway strike initial POC Co-authored-by: dutchie032 <dutchie032>
This commit is contained in:
@@ -0,0 +1 @@
|
||||
**/.DS_Store
|
||||
Vendored
+10
-1
@@ -6,5 +6,14 @@
|
||||
],
|
||||
"Lua.diagnostics.globals": [
|
||||
"lfs"
|
||||
]
|
||||
],
|
||||
"maptz.regionfolder": {
|
||||
"[lua]" : {
|
||||
"foldEnd": "--endregion",
|
||||
"foldEndRegex": "[\\s]*--endregion",
|
||||
"foldStart": "--region [NAME]",
|
||||
"foldStartRegex": "[\\s]*--region[\\s]*(.*)",
|
||||
"disableFolding": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -292,10 +292,25 @@
|
||||
Missions can be randomized using the <span class="inline-lua"><span class="lua-variable">RANDOMMISSION</span></span> prefix. <br />
|
||||
Spearhead will pick one random mission from zones with the same name.
|
||||
</p>
|
||||
|
||||
<h2>Runway Bombing</h2>
|
||||
<p>
|
||||
Runway bombing can be a very effective OCA tactic. With Spearhead we've tried adding as much logic and detail to it so it will feel as engaging as possible. <br/>
|
||||
|
||||
Firstly, the runway is split up in 5 sections and only sections 2, 3 and 4 are then actually tracked. <br/>
|
||||
|
||||
<span style="font-family: Consolas, Monaco, 'Lucida Console', monospace;">
|
||||
+-----------+-----------+-----------+-----------+-----------+ <br/>
|
||||
| Section 1 | Section 2 | Section 3 | Section 4 | Section 5 | <br/>
|
||||
+-----------+-----------+-----------+-----------+-----------+ <br/>
|
||||
</span>
|
||||
|
||||
|
||||
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="clear: both;"></div>
|
||||
</main>
|
||||
<footer>
|
||||
<p>© 2025 Spearhead Project</p>
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,50 @@
|
||||
|
||||
---@class Queue
|
||||
---@field private _items Array
|
||||
---@field private _first number
|
||||
---@field private _last number
|
||||
local Queue = {}
|
||||
Queue.__index = Queue
|
||||
|
||||
---@return Queue
|
||||
function Queue.new()
|
||||
|
||||
local self = setmetatable({}, Queue)
|
||||
self._items = {}
|
||||
self._first = 1
|
||||
self._last = 0
|
||||
return self
|
||||
end
|
||||
|
||||
---@return nil
|
||||
function Queue:push(item)
|
||||
self._last = self._last + 1
|
||||
self._items[self._last] = item
|
||||
end
|
||||
|
||||
---@return any?
|
||||
function Queue:pop()
|
||||
if self._first > self._last then
|
||||
return nil
|
||||
end
|
||||
|
||||
local item = self._items[self._first]
|
||||
self._items[self._first] = nil
|
||||
self._first = self._first + 1
|
||||
return item
|
||||
end
|
||||
|
||||
|
||||
---@return Array<any>
|
||||
function Queue:toList()
|
||||
local items = {}
|
||||
for i = self._first, self._last do
|
||||
items[#items + 1] = self._items[i]
|
||||
end
|
||||
return items
|
||||
end
|
||||
|
||||
|
||||
if Spearhead == nil then Spearhead = {} end
|
||||
if Spearhead._baseClasses == nil then Spearhead._baseClasses = {} end
|
||||
Spearhead._baseClasses.Queue = Queue
|
||||
@@ -10,6 +10,8 @@
|
||||
---@field groupsByName table<string, CapGroup>
|
||||
---@field PrimaryGroups Array<CapGroup>
|
||||
---@field BackupGroups Array<CapGroup>
|
||||
---@field private runwayBombingTracker RunwayBombingTracker
|
||||
---@field private runwayStrikeMissions table<string, RunwayStrikeMission>
|
||||
local CapBase = {}
|
||||
|
||||
|
||||
@@ -23,14 +25,17 @@ end
|
||||
---@param logger table
|
||||
---@param capConfig table
|
||||
---@param stageConfig table
|
||||
---@param runwayBombingTracker RunwayBombingTracker
|
||||
---@return CapBase
|
||||
function CapBase.new(airbaseName, database, logger, capConfig, stageConfig)
|
||||
function CapBase.new(airbaseName, database, logger, capConfig, stageConfig, runwayBombingTracker)
|
||||
|
||||
CapBase.__index = CapBase
|
||||
local self = setmetatable({}, { __index = CapBase }) --[[@as CapBase]]
|
||||
|
||||
self.groupNames = database:getCapGroupsAtAirbase(airbaseName)
|
||||
self.database = database
|
||||
self.runwayBombingTracker = runwayBombingTracker
|
||||
self.runwayStrikeMissions = {}
|
||||
|
||||
self.airbaseName = airbaseName
|
||||
self.logger = logger
|
||||
@@ -44,7 +49,7 @@ function CapBase.new(airbaseName, database, logger, capConfig, stageConfig)
|
||||
self.BackupGroups = {}
|
||||
|
||||
for key, name in pairs(self.groupNames) do
|
||||
local capGroup = Spearhead.internal.CapGroup.new(name, airbaseName, logger, database, capConfig)
|
||||
local capGroup = Spearhead.classes.capClasses.CapGroup.new(name, airbaseName, logger, database, capConfig)
|
||||
if capGroup then
|
||||
self.groupsByName[name] = capGroup
|
||||
|
||||
@@ -57,21 +62,40 @@ function CapBase.new(airbaseName, database, logger, capConfig, stageConfig)
|
||||
capGroup:AddOnStateUpdatedListener(self)
|
||||
end
|
||||
end
|
||||
logger:info("Airbase with name '" .. airbaseName .. "' has a total of " .. Spearhead.Util.tableLength(self.groupsByName) .. "cap flights registered")
|
||||
logger:info("Airbase with name '" .. airbaseName .. "' has a total of " .. Spearhead.Util.tableLength(self.groupsByName) .. " cap flights registered")
|
||||
|
||||
self:CreateRunwayStrikeMission()
|
||||
|
||||
Spearhead.Events.AddStageNumberChangedListener(self)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
---@private
|
||||
function CapBase:CreateRunwayStrikeMission()
|
||||
|
||||
local airbase = Airbase.getByName(self.airbaseName)
|
||||
if not airbase then
|
||||
self.logger:debug("Could not find a airbase with name to create runway mission" .. self.airbaseName)
|
||||
return
|
||||
end
|
||||
|
||||
for _, runway in pairs(airbase:getRunways()) do
|
||||
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)
|
||||
local mission = Spearhead.classes.stageClasses.missions.RunwayStrikeMission.new(runway, self.airbaseName, self.database, self.logger, self.runwayBombingTracker)
|
||||
self.runwayStrikeMissions[runway.Name] = mission
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function CapBase:onGroupStateUpdated(capGroup)
|
||||
--[[
|
||||
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.
|
||||
]]--
|
||||
if capGroup.state == Spearhead.internal.CapGroup.GroupState.INTRANSIT
|
||||
or capGroup.state == Spearhead.internal.CapGroup.GroupState.ONSTATION
|
||||
or capGroup.state == Spearhead.internal.CapGroup.GroupState.REARMING
|
||||
if capGroup.state == Spearhead.classes.capClasses.CapGroup.GroupState.INTRANSIT
|
||||
or capGroup.state == Spearhead.classes.capClasses.CapGroup.GroupState.ONSTATION
|
||||
or capGroup.state == Spearhead.classes.capClasses.CapGroup.GroupState.REARMING
|
||||
then
|
||||
return
|
||||
end
|
||||
@@ -84,7 +108,7 @@ function CapBase:SpawnIfApplicable()
|
||||
|
||||
local targetStage = capGroup:GetTargetZone(self.activeStage)
|
||||
|
||||
if targetStage ~= nil and capGroup.state == Spearhead.internal.CapGroup.GroupState.UNSPAWNED then
|
||||
if targetStage ~= nil and capGroup.state == Spearhead.classes.capClasses.CapGroup.GroupState.UNSPAWNED then
|
||||
capGroup:SpawnOnTheRamp()
|
||||
end
|
||||
end
|
||||
@@ -99,7 +123,7 @@ function CapBase:CheckAndScheduleCAP()
|
||||
|
||||
--Count back up groups that are active or reassign to the new zone if that's needed
|
||||
for _, backupGroup in pairs(self.BackupGroups) do
|
||||
if backupGroup.state == Spearhead.internal.CapGroup.GroupState.INTRANSIT or backupGroup.state == Spearhead.internal.CapGroup.GroupState.ONSTATION then
|
||||
if backupGroup.state == Spearhead.classes.capClasses.CapGroup.GroupState.INTRANSIT or backupGroup.state == Spearhead.classes.capClasses.CapGroup.GroupState.ONSTATION then
|
||||
local supposedTargetStage = backupGroup:GetTargetZone(self.activeStage)
|
||||
if supposedTargetStage then
|
||||
if supposedTargetStage ~= backupGroup.assignedStageNumber then
|
||||
@@ -113,7 +137,7 @@ function CapBase:CheckAndScheduleCAP()
|
||||
else
|
||||
backupGroup:SendRTBAndDespawn()
|
||||
end
|
||||
elseif backupGroup.state == Spearhead.internal.CapGroup.GroupState.RTBINTEN and backupGroup:GetTargetZone(self.activeStage) ~= backupGroup.assignedStageNumber then
|
||||
elseif backupGroup.state == Spearhead.classes.capClasses.CapGroup.GroupState.RTBINTEN and backupGroup:GetTargetZone(self.activeStage) ~= backupGroup.assignedStageNumber then
|
||||
backupGroup:SendRTB()
|
||||
end
|
||||
end
|
||||
@@ -133,12 +157,12 @@ function CapBase:CheckAndScheduleCAP()
|
||||
|
||||
requiredPerStage[supposedTargetStage] = requiredPerStage[supposedTargetStage] + 1
|
||||
|
||||
if primaryGroup.state == Spearhead.internal.CapGroup.GroupState.READYONRAMP then
|
||||
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.internal.CapGroup.GroupState.INTRANSIT or primaryGroup.state == Spearhead.internal.CapGroup.GroupState.ONSTATION then
|
||||
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)
|
||||
@@ -148,7 +172,7 @@ function CapBase:CheckAndScheduleCAP()
|
||||
end
|
||||
end
|
||||
countPerStage[supposedTargetStage] = countPerStage[supposedTargetStage] + 1
|
||||
elseif primaryGroup.state == Spearhead.internal.CapGroup.GroupState.RTBINTEN and primaryGroup:GetTargetZone(self.activeStage) ~= primaryGroup.assignedStageNumber then
|
||||
elseif primaryGroup.state == Spearhead.classes.capClasses.CapGroup.GroupState.RTBINTEN and primaryGroup:GetTargetZone(self.activeStage) ~= primaryGroup.assignedStageNumber then
|
||||
primaryGroup:SendRTB()
|
||||
end
|
||||
else
|
||||
@@ -157,7 +181,7 @@ function CapBase:CheckAndScheduleCAP()
|
||||
end
|
||||
|
||||
for _, backupGroup in pairs(self.BackupGroups) do
|
||||
if backupGroup.state == Spearhead.internal.CapGroup.GroupState.READYONRAMP then
|
||||
if backupGroup.state == Spearhead.classes.capClasses.CapGroup.GroupState.READYONRAMP then
|
||||
local supposedTargetStage = backupGroup:GetTargetZone(self.activeStage)
|
||||
if supposedTargetStage then
|
||||
if countPerStage[supposedTargetStage] == nil then
|
||||
@@ -177,6 +201,13 @@ function CapBase:CheckAndScheduleCAP()
|
||||
|
||||
function CapBase:OnStageNumberChanged(number)
|
||||
self.activeStage = number
|
||||
|
||||
if self:IsBaseActiveWhenStageIsActive(number) == true then
|
||||
for _, mission in pairs(self.runwayStrikeMissions) do
|
||||
mission:SpawnActive()
|
||||
end
|
||||
end
|
||||
|
||||
self:SpawnIfApplicable()
|
||||
timer.scheduleFunction(CheckReschedulingAsync, self, timer.getTime() + 5)
|
||||
end
|
||||
@@ -195,5 +226,7 @@ function CapBase:IsBaseActiveWhenStageIsActive(stageNumber)
|
||||
end
|
||||
|
||||
|
||||
if not Spearhead.internal then Spearhead.internal = {} end
|
||||
Spearhead.internal.CapAirbase = CapBase
|
||||
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.CapAirbase = CapBase
|
||||
|
||||
@@ -261,6 +261,11 @@ function CapGroup:SendToStage(stageZoneNumber)
|
||||
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
|
||||
@@ -418,5 +423,7 @@ function CapGroup:OnUnitLost(initiatorUnit)
|
||||
end
|
||||
|
||||
|
||||
if not Spearhead.internal then Spearhead.internal = {} end
|
||||
Spearhead.internal.CapGroup = CapGroup
|
||||
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
|
||||
|
||||
@@ -9,12 +9,15 @@ do
|
||||
|
||||
---comment
|
||||
---@param database Database
|
||||
---@param capConfig any
|
||||
---@param stageConfig any
|
||||
function GlobalCapManager.start(database, capConfig, stageConfig)
|
||||
---@param capConfig table
|
||||
---@param stageConfig StageConfig
|
||||
---@param logLevel LogLevel
|
||||
function GlobalCapManager.start(database, capConfig, stageConfig, logLevel)
|
||||
if initiated == true then return end
|
||||
|
||||
local logger = Spearhead.LoggerTemplate.new("AirbaseManager", capConfig.logLevel)
|
||||
local logger = Spearhead.LoggerTemplate.new("AirbaseManager", logLevel)
|
||||
local bombTrackLogger = Spearhead.LoggerTemplate.new("RunwayBombingTracker", logLevel)
|
||||
local runwayBombingTracker = Spearhead.classes.capClasses.runwayBombing.RunwayBombingTracker.new(bombTrackLogger)
|
||||
|
||||
local zones = database:getStagezoneNames()
|
||||
if zones then
|
||||
@@ -27,8 +30,8 @@ do
|
||||
if airbaseNames then
|
||||
for _, airbaseName in pairs(airbaseNames) do
|
||||
if airbaseName then
|
||||
local airbaseSpecificLogger = Spearhead.LoggerTemplate.new("CAP_" .. airbaseName, capConfig.logLevel)
|
||||
local airbase = Spearhead.internal.CapAirbase.new(airbaseName, database, airbaseSpecificLogger, capConfig, stageConfig)
|
||||
local airbaseSpecificLogger = Spearhead.LoggerTemplate.new("CAP_" .. airbaseName, logLevel)
|
||||
local airbase = Spearhead.classes.capClasses.CapAirbase.new(airbaseName, database, airbaseSpecificLogger, capConfig, stageConfig, runwayBombingTracker)
|
||||
if airbase then
|
||||
table.insert(airbasesPerStage[stageName], airbase)
|
||||
allAirbasesByName[airbaseName] = airbase
|
||||
@@ -61,5 +64,9 @@ do
|
||||
end
|
||||
end
|
||||
|
||||
if not Spearhead.internal then Spearhead.internal = {} end
|
||||
Spearhead.internal.GlobalCapManager = GlobalCapManager
|
||||
|
||||
|
||||
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.GlobalCapManager = GlobalCapManager
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
---@class RunwayBombingTracker : OnWeaponFiredListener
|
||||
---@field trackedRunways table<Runway, RunwayStrikeMission>
|
||||
---@field private _logger Logger
|
||||
local RunwayBombingTracker = {}
|
||||
RunwayBombingTracker.__index = RunwayBombingTracker
|
||||
|
||||
--- Constructor
|
||||
--- @param logger Logger
|
||||
--- @return RunwayBombingTracker
|
||||
function RunwayBombingTracker.new(logger)
|
||||
local self = setmetatable({}, RunwayBombingTracker)
|
||||
|
||||
self._logger = logger
|
||||
|
||||
Spearhead.Events.AddWeaponFiredListener(self)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
---comment
|
||||
---@param weapon Weapon
|
||||
function RunwayBombingTracker:OnWeaponFired(unit, weapon, target)
|
||||
|
||||
if weapon == nil then
|
||||
return
|
||||
end
|
||||
|
||||
self._logger:debug("Tracking weapon for runway impact onWeaponFired")
|
||||
|
||||
local desc = weapon:getDesc()
|
||||
|
||||
local isTrackable = desc.category == Weapon.Category.BOMB or (desc.category == Weapon.Category.MISSILE and desc.missileCategory == Weapon.MissileCategory.CRUISE)
|
||||
|
||||
if isTrackable == true then
|
||||
|
||||
---@type WeaponTrackingArgs
|
||||
local weaponTrackingArgs = {
|
||||
weapon = weapon,
|
||||
self = self
|
||||
}
|
||||
|
||||
timer.scheduleFunction(RunwayBombingTracker.trackWeaponTask, weaponTrackingArgs, timer.getTime() + 1)
|
||||
end
|
||||
end
|
||||
|
||||
function RunwayBombingTracker:RegisterRunway(runway, strikeMission)
|
||||
if not self.trackedRunways then
|
||||
self.trackedRunways = {}
|
||||
end
|
||||
|
||||
if not self.trackedRunways[runway] then
|
||||
self.trackedRunways[runway] = strikeMission
|
||||
end
|
||||
end
|
||||
|
||||
---@class WeaponTrackingArgs
|
||||
---@field weapon Weapon
|
||||
---@field self RunwayBombingTracker
|
||||
|
||||
---@private
|
||||
---@param weaponTrackingArgs WeaponTrackingArgs
|
||||
function RunwayBombingTracker.trackWeaponTask(weaponTrackingArgs, time)
|
||||
|
||||
local weapon = weaponTrackingArgs.weapon
|
||||
local self = weaponTrackingArgs.self
|
||||
|
||||
if not weapon then return nil end
|
||||
|
||||
local pos = weapon:getPoint()
|
||||
local velocity = weapon:getVelocity()
|
||||
local ground = land.getHeight({ x = pos.x, y = pos.z })
|
||||
local MpS = velocity.y -- increase the speed to make sure you don't miss it.
|
||||
|
||||
if MpS > 0 then
|
||||
return time + 3
|
||||
end
|
||||
|
||||
local nextInterval = (pos.y - ground) / math.abs(MpS)
|
||||
|
||||
if nextInterval < 1 then
|
||||
|
||||
-- Calculate the impact point of the weapon
|
||||
---@type Vec2
|
||||
local impactPoint = {
|
||||
x = pos.x + velocity.x * nextInterval,
|
||||
y = pos.z + velocity.z * nextInterval
|
||||
}
|
||||
|
||||
|
||||
self:OnWeaponImpact(weapon:getDesc(), impactPoint)
|
||||
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
if nextInterval > 5 then
|
||||
nextInterval = 5
|
||||
end
|
||||
|
||||
return time + (nextInterval /2)
|
||||
end
|
||||
|
||||
|
||||
---comment
|
||||
---@param weaponDesc table
|
||||
---@param impactPoint Vec2
|
||||
function RunwayBombingTracker:OnWeaponImpact(weaponDesc, impactPoint)
|
||||
|
||||
self._logger:debug("RunwayBombingTracker:OnWeaponImpact")
|
||||
|
||||
local warhead = weaponDesc.warhead
|
||||
local explosiveMass = warhead.explosiveMass or warhead.shapedExplosiveMass
|
||||
|
||||
for runway, strikeMission in pairs(self.trackedRunways) do
|
||||
|
||||
local zone= strikeMission:GetRunwayZone()
|
||||
|
||||
if Spearhead.Util.is3dPointInZone({ x = impactPoint.x, z = impactPoint.y, y = 0 }, zone) then
|
||||
strikeMission:RunwayHit(impactPoint, explosiveMass)
|
||||
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.runwayBombing then Spearhead.classes.capClasses.runwayBombing = {} end
|
||||
Spearhead.classes.capClasses.runwayBombing.RunwayBombingTracker = RunwayBombingTracker
|
||||
@@ -2,10 +2,11 @@
|
||||
--- @class StageConfig
|
||||
--- @field isEnabled boolean
|
||||
--- @field isDrawStagesEnabled boolean
|
||||
--- @field isDrawPreActivatedEnabled boolean
|
||||
--- @field isAutoStages boolean
|
||||
--- @field startingStage integer
|
||||
--- @field maxMissionsPerStage integer
|
||||
--- @field logLevel LogLevel
|
||||
--- @field AmountPreactivateStage integer
|
||||
|
||||
|
||||
local StageConfig = {};
|
||||
@@ -24,13 +25,10 @@ function StageConfig:new()
|
||||
isAutoStages = SpearheadConfig.StageConfig.autoStages or true,
|
||||
startingStage = SpearheadConfig.StageConfig.startingStage or 1,
|
||||
maxMissionsPerStage = SpearheadConfig.StageConfig.maxMissionStage or 10,
|
||||
logLevel = "INFO"
|
||||
isDrawPreActivatedEnabled = SpearheadConfig.StageConfig.drawPreActivated or true,
|
||||
AmountPreactivateStage = SpearheadConfig.StageConfig.preactivateStage or 1,
|
||||
}
|
||||
|
||||
if SpearheadConfig.StageConfig.debugEnabled == true then
|
||||
o.logLevel = "DEBUG"
|
||||
end
|
||||
|
||||
|
||||
setmetatable(o, { __index = self })
|
||||
|
||||
return o;
|
||||
|
||||
@@ -3,23 +3,6 @@
|
||||
|
||||
do -- mission aliases
|
||||
|
||||
--- @alias MissionPriority
|
||||
--- | "none"
|
||||
--- | "primary"
|
||||
--- | "secondary"
|
||||
|
||||
--- @alias missionType
|
||||
--- | "nil"
|
||||
--- | "STRIKE"
|
||||
--- | "BAI"
|
||||
--- | "DEAD"
|
||||
--- | "SAM"
|
||||
|
||||
--- @alias MissionState
|
||||
--- | "NEW"
|
||||
--- | "ACTIVE"
|
||||
--- | "COMPLETED"
|
||||
|
||||
--- @alias LogLevel
|
||||
--- | "DEBUG"
|
||||
--- | "INFO"
|
||||
@@ -27,8 +10,6 @@ do -- mission aliases
|
||||
--- | "ERROR"
|
||||
--- | "NONE"
|
||||
|
||||
|
||||
|
||||
---@class Array<T>: { [integer]: T }
|
||||
|
||||
--- @class Position
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
local FleetGroup = {}
|
||||
|
||||
---comment
|
||||
---@param fleetGroupName string
|
||||
---@param database Database
|
||||
---@param logger Logger
|
||||
---@return nil
|
||||
function FleetGroup:new(fleetGroupName, database, logger)
|
||||
local o = {}
|
||||
|
||||
@@ -30,10 +35,10 @@ function FleetGroup:new(fleetGroupName, database, logger)
|
||||
Spearhead.AddMissionEditorWarning(
|
||||
"CARRIERROUTE should at least have 3 parts. Check the documentation for: " .. zoneName)
|
||||
else
|
||||
|
||||
---@param zone SpearheadTriggerZone
|
||||
---@return Vec2, Vec2
|
||||
local function GetTwoFurthestPoints(zone)
|
||||
local function getDist(a, b)
|
||||
return math.sqrt((b.x - a.x) ^ 2 + (b.z - a.z) ^ 2)
|
||||
end
|
||||
|
||||
local biggest = nil
|
||||
local biggestA = zone.verts[1]
|
||||
@@ -43,7 +48,7 @@ function FleetGroup:new(fleetGroupName, database, logger)
|
||||
for ii = i + 1, 4 do
|
||||
local a = zone.verts[i]
|
||||
local b = zone.verts[ii]
|
||||
local dist = getDist(a, b)
|
||||
local dist = Spearhead.Util.VectorDistance2d(a, b)
|
||||
|
||||
if biggest == nil or dist > biggest then
|
||||
biggestA = a
|
||||
@@ -52,7 +57,7 @@ function FleetGroup:new(fleetGroupName, database, logger)
|
||||
end
|
||||
end
|
||||
end
|
||||
return { x = biggestA.x, z = biggestA.z }, { x = biggestB.x, z = biggestB.z }
|
||||
return { x = biggestA.x, y = biggestA.y }, { x = biggestB.x, y = biggestB.y }
|
||||
end
|
||||
|
||||
local function getMinMaxStage(namePart)
|
||||
@@ -90,7 +95,7 @@ function FleetGroup:new(fleetGroupName, database, logger)
|
||||
for i = first, second do
|
||||
o.targetZonePerStage[tostring(i)] = zoneName
|
||||
end
|
||||
o.pointsPerZone[zoneName] = { pointA = pointA, pointB = pointB }
|
||||
o.pointsPerZone[zoneName] = { pointA = { x = pointA.x, z = pointA.y, y = 0 }, pointB = { x = pointB.x, z = pointB.y, y = 0} }
|
||||
else
|
||||
Spearhead.AddMissionEditorWarning("CARRIERROUTE zone stage numbers not in the format _[<number>-<number>]: " .. zoneName)
|
||||
end
|
||||
|
||||
+242
-183
@@ -82,7 +82,6 @@ do -- INIT UTIL
|
||||
---@param ignoreCase boolean?
|
||||
---@return boolean
|
||||
UTIL.startswith = function(str, findable, ignoreCase)
|
||||
|
||||
if ignoreCase == true then
|
||||
return string.lower(str):find('^' .. string.lower(findable)) ~= nil
|
||||
end
|
||||
@@ -122,8 +121,8 @@ do -- INIT UTIL
|
||||
end
|
||||
|
||||
---comment
|
||||
---@param a Vec2 DCS Point vector {x, z , y}
|
||||
---@param b Vec2 DCS Point vector {x, z , y}
|
||||
---@param a Vec2
|
||||
---@param b Vec2
|
||||
---@return number
|
||||
function UTIL.VectorDistance2d(a, b)
|
||||
return math.sqrt((b.x - a.x) ^ 2 + (b.y - a.y) ^ 2)
|
||||
@@ -144,95 +143,118 @@ do -- INIT UTIL
|
||||
return (vec.x ^ 2 + vec.y ^ 2 + vec.z ^ 2) ^ 0.5
|
||||
end
|
||||
|
||||
|
||||
---comment
|
||||
---@param polygon table of pairs { x, z }
|
||||
---@param x number X location
|
||||
---@param z number Y location
|
||||
---@return boolean
|
||||
function UTIL.IsPointInPolygon(polygon, x, z)
|
||||
local function isInComplexPolygon(polygon, x, z)
|
||||
local function getEdges(poly)
|
||||
local result = {}
|
||||
for i = 1, #poly do
|
||||
local point1 = poly[i]
|
||||
local point2Index = i + 1
|
||||
if point2Index > #poly then point2Index = 1 end
|
||||
local point2 = poly[point2Index]
|
||||
local edge = { x1 = point1.x, z1 = point1.z, x2 = point2.x, z2 = point2.z }
|
||||
table.insert(result, edge)
|
||||
end
|
||||
return result
|
||||
local function isInComplexPolygon(polygon, x, y)
|
||||
local function getEdges(poly)
|
||||
local result = {}
|
||||
for i = 1, #poly do
|
||||
local point1 = poly[i]
|
||||
local point2Index = i + 1
|
||||
if point2Index > #poly then point2Index = 1 end
|
||||
local point2 = poly[point2Index]
|
||||
local edge = { x1 = point1.x, z1 = point1.y, x2 = point2.x, z2 = point2.y }
|
||||
table.insert(result, edge)
|
||||
end
|
||||
|
||||
local edges = getEdges(polygon)
|
||||
local count = 0;
|
||||
for _, edge in pairs(edges) do
|
||||
if (x < edge.x1) ~= (x < edge.x2) and z < edge.z1 + ((x - edge.x1) / (edge.x2 - edge.x1)) * (edge.z2 - edge.z1) then
|
||||
count = count + 1
|
||||
-- if (yp < y1) != (yp < y2) and xp < x1 + ((yp-y1)/(y2-y1))*(x2-x1) then
|
||||
-- count = count + 1
|
||||
end
|
||||
end
|
||||
return count % 2 == 1
|
||||
return result
|
||||
end
|
||||
return isInComplexPolygon(polygon, x, z)
|
||||
|
||||
local edges = getEdges(polygon)
|
||||
local count = 0;
|
||||
for _, edge in pairs(edges) do
|
||||
if (x < edge.x1) ~= (x < edge.x2) and y < edge.z1 + ((x - edge.x1) / (edge.x2 - edge.x1)) * (edge.z2 - edge.z1) then
|
||||
count = count + 1
|
||||
-- if (yp < y1) != (yp < y2) and xp < x1 + ((yp-y1)/(y2-y1))*(x2-x1) then
|
||||
-- count = count + 1
|
||||
end
|
||||
end
|
||||
return count % 2 == 1
|
||||
end
|
||||
|
||||
---comment
|
||||
---@param points table points { x, z }
|
||||
---@return table hullPoints { x, z }
|
||||
---@param polygon Array<Vec2> of pairs { x, y }
|
||||
---@param x number X location
|
||||
---@param y number Y location
|
||||
---@return boolean
|
||||
function UTIL.IsPointInPolygon(polygon, x, y)
|
||||
return isInComplexPolygon(polygon, x, y)
|
||||
end
|
||||
|
||||
---@param point Vec3
|
||||
---@param zone SpearheadTriggerZone
|
||||
function UTIL.is3dPointInZone(point, zone)
|
||||
if zone.zone_type == "Polygon" and zone.verts then
|
||||
if UTIL.IsPointInPolygon(zone.verts, point.x, point.z) == true then
|
||||
return true
|
||||
end
|
||||
else
|
||||
if (((point.x - zone.location.x) ^ 2 + (point.z - zone.location.y) ^ 2) ^ 0.5 <= zone.radius) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
---comment
|
||||
---@param points Array<Vec2> points
|
||||
---@return Array<Vec2> hullPoints
|
||||
function UTIL.getConvexHull(points)
|
||||
if #points == 0 then
|
||||
return {}
|
||||
end
|
||||
|
||||
local function ccw(a,b,c)
|
||||
return (b.z - a.z) * (c.x - a.x) > (b.x - a.x) * (c.z - a.z)
|
||||
---comment
|
||||
---@param a Vec2
|
||||
---@param b Vec2
|
||||
---@param c Vec2
|
||||
---@return boolean
|
||||
local function ccw(a, b, c)
|
||||
return (b.y - a.y) * (c.x - a.x) > (b.x - a.x) * (c.y - a.y)
|
||||
end
|
||||
|
||||
table.sort(points, function(left,right)
|
||||
return left.z < right.z
|
||||
table.sort(points, function(left, right)
|
||||
return left.y < right.y
|
||||
end)
|
||||
|
||||
local hull = {}
|
||||
-- lower hull
|
||||
for _,point in pairs(points) do
|
||||
while #hull >= 2 and not ccw(hull[#hull-1], hull[#hull], point) do
|
||||
table.remove(hull,#hull)
|
||||
for _, point in pairs(points) do
|
||||
while #hull >= 2 and not ccw(hull[#hull - 1], hull[#hull], point) do
|
||||
table.remove(hull, #hull)
|
||||
end
|
||||
table.insert(hull,point)
|
||||
table.insert(hull, point)
|
||||
end
|
||||
|
||||
-- upper hull
|
||||
local t = #hull + 1
|
||||
for i=#points, 1, -1 do
|
||||
for i = #points, 1, -1 do
|
||||
local point = points[i]
|
||||
while #hull >= t and not ccw(hull[#hull-1], hull[#hull], point) do
|
||||
table.remove(hull,#hull)
|
||||
while #hull >= t and not ccw(hull[#hull - 1], hull[#hull], point) do
|
||||
table.remove(hull, #hull)
|
||||
end
|
||||
table.insert(hull,point)
|
||||
table.insert(hull, point)
|
||||
end
|
||||
table.remove(hull,#hull)
|
||||
table.remove(hull, #hull)
|
||||
return hull
|
||||
end
|
||||
|
||||
---@param points Array<Vec2>
|
||||
function UTIL.enlargeConvexHull(points, meters)
|
||||
---@type Array<Vec2>
|
||||
local allpoints = {}
|
||||
|
||||
local allpoints = {}
|
||||
|
||||
for _, point in pairs(points) do
|
||||
table.insert(allpoints, point)
|
||||
table.insert(allpoints, { x = point.x + meters, z = point.z, y= 0 })
|
||||
table.insert(allpoints, { x = point.x - meters, z = point.z, y= 0 })
|
||||
table.insert(allpoints, { x = point.x, z = point.z + meters, y= 0 })
|
||||
table.insert(allpoints, { x = point.x, z = point.z - meters, y= 0 })
|
||||
|
||||
table.insert(allpoints, { x = point.x + math.cos(math.rad(45)) * meters, z = point.z + math.sin(math.rad(45)) * meters, y= 0 })
|
||||
table.insert(allpoints, { x = point.x - math.cos(math.rad(45)) * meters, z = point.z - math.sin(math.rad(45)) * meters, y= 0 })
|
||||
table.insert(allpoints, { x = point.x - math.cos(math.rad(45)) * meters, z = point.z + math.sin(math.rad(45)) * meters, y= 0 })
|
||||
table.insert(allpoints, { x = point.x + math.cos(math.rad(45)) * meters, z = point.z - math.sin(math.rad(45)) * meters, y= 0 })
|
||||
allpoints[#allpoints + 1] = point
|
||||
allpoints[#allpoints+1] = { x = point.x + meters, y = point.y, }
|
||||
allpoints[#allpoints+1] = { x = point.x - meters, y = point.y, }
|
||||
allpoints[#allpoints+1] = { x = point.x, y = point.y + meters, }
|
||||
allpoints[#allpoints+1] = { x = point.x, y = point.y - meters, }
|
||||
|
||||
allpoints[#allpoints+1] = { x = point.x + math.cos(math.rad(45)) * meters, y = point.y + math.sin(math.rad(45)) * meters, }
|
||||
allpoints[#allpoints+1] = { x = point.x - math.cos(math.rad(45)) * meters, y = point.y - math.sin(math.rad(45)) * meters, }
|
||||
allpoints[#allpoints+1] = { x = point.x - math.cos(math.rad(45)) * meters, y = point.y + math.sin(math.rad(45)) * meters, }
|
||||
allpoints[#allpoints+1] = { x = point.x + math.cos(math.rad(45)) * meters, y = point.y - math.sin(math.rad(45)) * meters, }
|
||||
end
|
||||
|
||||
return UTIL.getConvexHull(allpoints)
|
||||
@@ -262,12 +284,25 @@ do -- INIT DCS_UTIL
|
||||
|
||||
zone_type,
|
||||
x,
|
||||
z,
|
||||
y,
|
||||
radius
|
||||
verts,
|
||||
|
||||
}
|
||||
]] --
|
||||
|
||||
---@alias SpearheadTriggerZoneType
|
||||
---| "Cilinder"
|
||||
---| "Polygon"
|
||||
|
||||
---@class SpearheadTriggerZone
|
||||
---@field name string
|
||||
---@field location Vec2
|
||||
---@field radius number
|
||||
---@field verts Array<Vec2>
|
||||
---@field zone_type SpearheadTriggerZoneType
|
||||
|
||||
---@type Array<SpearheadTriggerZone>
|
||||
DCS_UTIL.__trigger_zones = {}
|
||||
end
|
||||
|
||||
@@ -293,18 +328,9 @@ do -- INIT DCS_UTIL
|
||||
}
|
||||
|
||||
DCS_UTIL.__airbaseNamesById = {}
|
||||
--[[
|
||||
zone = {
|
||||
name,
|
||||
zone_type,
|
||||
x,
|
||||
z,
|
||||
radius
|
||||
verts,
|
||||
|
||||
}
|
||||
]] --
|
||||
DCS_UTIL.__airbaseZonesById = {}
|
||||
---@type table<string, SpearheadTriggerZone>
|
||||
DCS_UTIL.__airbaseZonesByName = {}
|
||||
|
||||
DCS_UTIL.__airportsStartingCoalition = {}
|
||||
DCS_UTIL.__warehouseStartingCoalition = {}
|
||||
@@ -362,21 +388,25 @@ do -- INIT DCS_UTIL
|
||||
|
||||
do --init trigger zones
|
||||
for i, trigger_zone in pairs(env.mission.triggers.zones) do
|
||||
|
||||
-- reorder verts as they are not ordered correctly in the ME
|
||||
verts = {}
|
||||
if Spearhead.Util.tableLength(trigger_zone.verticies) >=4 then
|
||||
table.insert(verts, { x = trigger_zone.verticies[4].x , z = trigger_zone.verticies[4].y })
|
||||
table.insert(verts, { x = trigger_zone.verticies[3].x , z = trigger_zone.verticies[3].y })
|
||||
table.insert(verts, { x = trigger_zone.verticies[2].x , z = trigger_zone.verticies[2].y })
|
||||
table.insert(verts, { x = trigger_zone.verticies[1].x , z = trigger_zone.verticies[1].y })
|
||||
local verts = {}
|
||||
if Spearhead.Util.tableLength(trigger_zone.verticies) >= 4 then
|
||||
table.insert(verts, { x = trigger_zone.verticies[4].x, y = trigger_zone.verticies[4].y })
|
||||
table.insert(verts, { x = trigger_zone.verticies[3].x, y = trigger_zone.verticies[3].y })
|
||||
table.insert(verts, { x = trigger_zone.verticies[2].x, y = trigger_zone.verticies[2].y })
|
||||
table.insert(verts, { x = trigger_zone.verticies[1].x, y = trigger_zone.verticies[1].y })
|
||||
end
|
||||
|
||||
|
||||
local zoneType = "Cilinder"
|
||||
if trigger_zone.type == DCS_UTIL.ZoneType.Polygon then
|
||||
zoneType = "Polygon"
|
||||
end
|
||||
|
||||
---@type SpearheadTriggerZone
|
||||
local zone = {
|
||||
name = trigger_zone.name,
|
||||
zone_type = trigger_zone.type,
|
||||
x = trigger_zone.x,
|
||||
z = trigger_zone.y,
|
||||
zone_type = zoneType,
|
||||
location = { x = trigger_zone.x, y = trigger_zone.y },
|
||||
radius = trigger_zone.radius,
|
||||
verts = verts
|
||||
}
|
||||
@@ -408,35 +438,36 @@ do -- INIT DCS_UTIL
|
||||
end
|
||||
end
|
||||
|
||||
do -- fill airbaseNames and zones
|
||||
do -- fill airbaseNames and zones
|
||||
local airbases = world.getAirbases()
|
||||
if airbases then
|
||||
for _, airbase in pairs(airbases) do
|
||||
local name = airbase:getName()
|
||||
local id = tostring(airbase:getID())
|
||||
|
||||
if name and id then
|
||||
DCS_UTIL.__airbaseNamesById[id] = name
|
||||
DCS_UTIL.__airbaseNamesById[tostring(airbase:getID())] = name
|
||||
|
||||
if name then
|
||||
local relevantPoints = {}
|
||||
for _, x in pairs(airbase:getRunways()) do
|
||||
if x.position and x.position.x and x.position.z then
|
||||
table.insert(relevantPoints, { x = x.position.x, z = x.position.z, y=0})
|
||||
table.insert(relevantPoints, { x = x.position.x, z = x.position.z, y = 0 })
|
||||
end
|
||||
end
|
||||
|
||||
for _, x in pairs(airbase:getParking()) do
|
||||
if x.vTerminalPos and x.vTerminalPos.x and x.vTerminalPos.z then
|
||||
table.insert(relevantPoints, { x = x.vTerminalPos.x, z = x.vTerminalPos.z, y=0})
|
||||
table.insert(relevantPoints, { x = x.vTerminalPos.x, z = x.vTerminalPos.z, y = 0 })
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local points = UTIL.getConvexHull(relevantPoints)
|
||||
local enlargedPoints = UTIL.enlargeConvexHull(points, 750)
|
||||
|
||||
DCS_UTIL.__airbaseZonesById[id] = {
|
||||
DCS_UTIL.__airbaseZonesByName[name] = {
|
||||
name = name,
|
||||
zone_type = DCS_UTIL.ZoneType.Polygon,
|
||||
location = { x = airbase:getPoint().x, y = airbase:getPoint().z },
|
||||
zone_type = "Polygon",
|
||||
radius = 0,
|
||||
verts = enlargedPoints
|
||||
}
|
||||
end
|
||||
@@ -484,7 +515,7 @@ do -- INIT DCS_UTIL
|
||||
end
|
||||
|
||||
---destroy the given group
|
||||
---@param groupName string
|
||||
---@param groupName string
|
||||
function DCS_UTIL.DestroyGroup(groupName)
|
||||
if DCS_UTIL.IsGroupStatic(groupName) then
|
||||
local object = StaticObject.getByName(groupName)
|
||||
@@ -514,13 +545,14 @@ do -- INIT DCS_UTIL
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- takes a list of units and returns all the units that are in any of the zones
|
||||
---@param unit_names table unit names
|
||||
---@param zone_names table zone names
|
||||
---@return table unit list of objects { unit = UNIT, zone_name = zoneName}
|
||||
function DCS_UTIL.getUnitsInZones(unit_names, zone_names)
|
||||
local units = {}
|
||||
|
||||
---@type Array<SpearheadTriggerZone>
|
||||
local zones = {}
|
||||
|
||||
for k = 1, #unit_names do
|
||||
@@ -544,14 +576,9 @@ do -- INIT DCS_UTIL
|
||||
local lCat = Object.getCategory(lUnit)
|
||||
for zone_name, zone in pairs(zones) do
|
||||
if unit_pos and ((lCat == 1 and lUnit:isActive() == true) or lCat ~= 1) then -- it is a unit and is active or it is not a unit
|
||||
if zone.zone_type == DCS_UTIL.ZoneType.Polygon and zone.verts then
|
||||
if UTIL.IsPointInPolygon(zone.verts, unit_pos.x, unit_pos.z) == true then
|
||||
in_zone_units[#in_zone_units + 1] = { unit = lUnit, zone_name = zone.name }
|
||||
end
|
||||
else
|
||||
if (((unit_pos.x - zone.x) ^ 2 + (unit_pos.z - zone.z) ^ 2) ^ 0.5 <= zone.radius) then
|
||||
in_zone_units[#in_zone_units + 1] = { unit = lUnit, zone_name = zone.name }
|
||||
end
|
||||
local isInZone = UTIL.is3dPointInZone(unit_pos, zone)
|
||||
if isInZone == true then
|
||||
in_zone_units[#in_zone_units + 1] = { unit = lUnit, zone_name = zone.name }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -569,22 +596,13 @@ do -- INIT DCS_UTIL
|
||||
return {}
|
||||
end
|
||||
|
||||
-- MAP Just for mapping sake
|
||||
local custom_zone = {
|
||||
x = zone.x,
|
||||
z = zone.z,
|
||||
zone_type = zone.zone_type,
|
||||
radius = zone.radius,
|
||||
verts = zone.verts
|
||||
}
|
||||
|
||||
return DCS_UTIL.areGroupsInCustomZone(group_names, custom_zone)
|
||||
return DCS_UTIL.areGroupsInCustomZone(group_names, zone)
|
||||
end
|
||||
|
||||
--- takes a x, y poistion and checks if it is inside any of the zones
|
||||
---@param group_names table North South position
|
||||
---@param zone table { x, z, zonetype, radius, verts }
|
||||
---@return table groupnames list of groups that are in the zone
|
||||
---@param group_names Array<string> North South position
|
||||
---@param zone SpearheadTriggerZone
|
||||
---@return Array<string> groupnames list of groups that are in the zone
|
||||
function DCS_UTIL.areGroupsInCustomZone(group_names, zone)
|
||||
local units = {}
|
||||
if Spearhead.Util.tableLength(group_names) < 1 then return {} end
|
||||
@@ -606,14 +624,9 @@ do -- INIT DCS_UTIL
|
||||
local result_groups = {}
|
||||
for _, entry in pairs(units) do
|
||||
local pos = entry.unit:getPoint()
|
||||
if zone.zone_type == DCS_UTIL.ZoneType.Polygon and zone.verts then
|
||||
if UTIL.IsPointInPolygon(zone.verts, pos.x, pos.z) == true then
|
||||
table.insert(result_groups, entry.groupname)
|
||||
end
|
||||
else
|
||||
if (((pos.x - zone.x) ^ 2 + (pos.z - zone.z) ^ 2) ^ 0.5 <= zone.radius) then
|
||||
table.insert(result_groups, entry.groupname)
|
||||
end
|
||||
local isInZone = UTIL.is3dPointInZone(pos, zone)
|
||||
if isInZone == true then
|
||||
table.insert(result_groups, entry.groupname)
|
||||
end
|
||||
end
|
||||
return result_groups
|
||||
@@ -625,6 +638,7 @@ do -- INIT DCS_UTIL
|
||||
---@param zone_names table zone names
|
||||
---@return table zones list of objects { zone_name = zoneName}
|
||||
function DCS_UTIL.isPositionInZones(x, z, zone_names)
|
||||
---@type Array<SpearheadTriggerZone>
|
||||
local zones = {}
|
||||
for index, zone_name in pairs(zone_names) do
|
||||
local zone = DCS_UTIL.__trigger_zones[zone_name]
|
||||
@@ -635,14 +649,8 @@ do -- INIT DCS_UTIL
|
||||
|
||||
local result_zones = {}
|
||||
for zone_name, zone in pairs(zones) do
|
||||
if zone.zone_type == DCS_UTIL.ZoneType.Polygon and zone.verts then
|
||||
if UTIL.IsPointInPolygon(zone.verts, x, z) == true then
|
||||
result_zones[#result_zones + 1] = zone.name
|
||||
end
|
||||
else
|
||||
if (((x - zone.x) ^ 2 + (z - zone.z) ^ 2) ^ 0.5 <= zone.radius) then
|
||||
result_zones[#result_zones + 1] = zone.name
|
||||
end
|
||||
if UTIL.is3dPointInZone({ x = x, z = z, y = 0 }, zone) == true then
|
||||
result_zones[#result_zones + 1] = zone.name
|
||||
end
|
||||
end
|
||||
return result_zones
|
||||
@@ -655,14 +663,8 @@ do -- INIT DCS_UTIL
|
||||
---@return boolean result
|
||||
function DCS_UTIL.isPositionInZone(x, z, zone_name)
|
||||
local zone = DCS_UTIL.__trigger_zones[zone_name]
|
||||
if zone.zone_type == DCS_UTIL.ZoneType.Polygon and zone.verts then
|
||||
if UTIL.IsPointInPolygon(zone.verts, x, z) == true then
|
||||
return true
|
||||
end
|
||||
else
|
||||
if (((x - zone.x) ^ 2 + (z - zone.z) ^ 2) ^ 0.5 <= zone.radius) then
|
||||
return true
|
||||
end
|
||||
if UTIL.is3dPointInZone({ x = x, y = 0, z = z }, zone) then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
@@ -672,58 +674,29 @@ do -- INIT DCS_UTIL
|
||||
---@param parent_zone_name string
|
||||
---@return boolean result
|
||||
function DCS_UTIL.isZoneInZone(zone_name, parent_zone_name)
|
||||
local zoneA = DCS_UTIL.__trigger_zones[zone_name]
|
||||
local zoneB = DCS_UTIL.__trigger_zones[parent_zone_name]
|
||||
|
||||
if zoneB.zone_type == DCS_UTIL.ZoneType.Polygon and zoneB.verts then
|
||||
if UTIL.IsPointInPolygon(zoneB.verts, zoneA.x, zoneA.z) == true then
|
||||
return true
|
||||
end
|
||||
else
|
||||
if (((zoneA.x - zoneB.x) ^ 2 + (zoneA.z - zoneB.z) ^ 2) ^ 0.5 <= zoneB.radius) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
--- takes a x, y poistion and checks if it is inside any of the zones
|
||||
---@param x number North South position
|
||||
---@param z number West East position
|
||||
---@param zone table { x, z, zonetype, radius }
|
||||
---@return boolean result
|
||||
function DCS_UTIL.isPositionInCustomZone(x, z, zone)
|
||||
if zone.zone_type == DCS_UTIL.ZoneType.Polygon and zone.verts then
|
||||
if UTIL.IsPointInPolygon(zone.verts, x, z) == true then
|
||||
return true
|
||||
end
|
||||
else
|
||||
if (((x - zone.x) ^ 2 + (z - zone.z) ^ 2) ^ 0.5 <= zone.radius) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
local zoneA = DCS_UTIL.__trigger_zones[zone_name] --[[@as SpearheadTriggerZone]]
|
||||
if zoneA == nil then return false end
|
||||
local zoneB = DCS_UTIL.__trigger_zones[parent_zone_name] --[[@as SpearheadTriggerZone]]
|
||||
if zoneB == nil then return false end
|
||||
return UTIL.is3dPointInZone({ x = zoneA.location.x, y = 0, z = zoneA.location.y }, zoneB)
|
||||
end
|
||||
|
||||
---comment
|
||||
---@param zone_name any
|
||||
---@return table? zone { name,b zone_type, x, z, radius, verts }
|
||||
---@return SpearheadTriggerZone? zone { name,b zone_type, x, z, radius, verts }
|
||||
function DCS_UTIL.getZoneByName(zone_name)
|
||||
if zone_name == nil then return nil end
|
||||
return DCS_UTIL.__trigger_zones[zone_name]
|
||||
end
|
||||
|
||||
---comment
|
||||
---@param airbaseId any
|
||||
---@return table? zone { name,b zone_type, x, z, radius, verts }
|
||||
function DCS_UTIL.getAirbaseZoneById(airbaseId)
|
||||
local string = tostring(airbaseId)
|
||||
---@param airbaseName string
|
||||
---@return SpearheadTriggerZone? zone { name,b zone_type, x, z, radius, verts }
|
||||
function DCS_UTIL.getAirbaseZoneByName(airbaseName)
|
||||
if string == nil then return nil end
|
||||
return DCS_UTIL.__airbaseZonesById[string]
|
||||
return DCS_UTIL.__airbaseZonesByName[string]
|
||||
end
|
||||
|
||||
|
||||
|
||||
---maps the category name to the DCS group category
|
||||
---@param input string the name
|
||||
---@return integer?
|
||||
@@ -790,7 +763,7 @@ do -- INIT DCS_UTIL
|
||||
---@return Array<Unit> units
|
||||
function DCS_UTIL.getAllPlayerUnits()
|
||||
local units = {}
|
||||
for i = 0,2 do
|
||||
for i = 0, 2 do
|
||||
local players = coalition.getPlayers(i)
|
||||
for key, unit in pairs(players) do
|
||||
units[#units + 1] = unit
|
||||
@@ -809,7 +782,7 @@ do -- INIT DCS_UTIL
|
||||
|
||||
---get base from id
|
||||
---@param baseId number
|
||||
---@return table? table
|
||||
---@return Airbase? table
|
||||
function DCS_UTIL.getAirbaseById(baseId)
|
||||
local name = DCS_UTIL.getAirbaseName(baseId)
|
||||
if name == nil then return nil end
|
||||
@@ -817,7 +790,7 @@ do -- INIT DCS_UTIL
|
||||
end
|
||||
|
||||
---Get the starting coalition of a farp or airbase
|
||||
---@param airbase Airbase
|
||||
---@param airbase Airbase
|
||||
---@return number? coalition
|
||||
function DCS_UTIL.getStartingCoalition(airbase)
|
||||
if airbase == nil then
|
||||
@@ -865,6 +838,92 @@ do -- INIT DCS_UTIL
|
||||
end
|
||||
end
|
||||
|
||||
---@class DrawColor
|
||||
---@field r number
|
||||
---@field g number
|
||||
---@field b number
|
||||
---@field a number
|
||||
|
||||
local drawID = 400
|
||||
---@param zone SpearheadTriggerZone
|
||||
---@param lineColor DrawColor
|
||||
---@param fillColor DrawColor
|
||||
---@param lineStyle LineType
|
||||
---@return number drawID
|
||||
function DCS_UTIL.DrawZone(zone, lineColor, fillColor, lineStyle)
|
||||
if lineStyle == nil then lineStyle = 4 end
|
||||
drawID = drawID + 1
|
||||
if zone.zone_type == "Cilinder" then
|
||||
trigger.action.circleToAll(-1, drawID, { x = zone.location.x, y = 0, z = zone.location.y }, zone.radius,
|
||||
{ 0, 0, 0, 0 }, { 0, 0, 0, 0 }, lineStyle, true)
|
||||
else
|
||||
local functionString = "trigger.action.markupToAll(7, -1, " .. drawID .. ","
|
||||
for _, vecpoint in pairs(zone.verts) do
|
||||
functionString = functionString .. " { x=" .. vecpoint.x .. ", y=0,z=" .. vecpoint.y .. "},"
|
||||
end
|
||||
functionString = functionString .. "{0,1,0,1}, {0,1,0,1}, " .. lineStyle .. ")"
|
||||
|
||||
env.info(functionString)
|
||||
---@diagnostic disable-next-line: deprecated
|
||||
local f, err = loadstring(functionString)
|
||||
if f then
|
||||
f()
|
||||
else
|
||||
env.error("Something failed when drawing complex drawing" .. err)
|
||||
end
|
||||
end
|
||||
local fillColorMapped = {
|
||||
fillColor.r or 0,
|
||||
fillColor.g or 0,
|
||||
fillColor.b or 0,
|
||||
fillColor.a or 0.5
|
||||
}
|
||||
|
||||
local lineColorMapped = {
|
||||
lineColor.r or 0,
|
||||
lineColor.g or 0,
|
||||
lineColor.b or 0,
|
||||
lineColor.a or 1
|
||||
}
|
||||
|
||||
trigger.action.setMarkupColorFill(drawID, fillColorMapped)
|
||||
trigger.action.setMarkupColor(drawID, lineColorMapped)
|
||||
|
||||
return drawID
|
||||
end
|
||||
|
||||
---comment
|
||||
---@param drawID number
|
||||
---@param lineColor DrawColor
|
||||
function DCS_UTIL.SetLineColor(drawID, lineColor)
|
||||
local lineColorMapped = {
|
||||
lineColor.r or 0,
|
||||
lineColor.g or 0,
|
||||
lineColor.b or 0,
|
||||
lineColor.a or 1
|
||||
}
|
||||
trigger.action.setMarkupColor(drawID, lineColorMapped)
|
||||
end
|
||||
|
||||
---comment
|
||||
---@param drawID number
|
||||
---@param fillColor DrawColor
|
||||
function DCS_UTIL.SetFillColor(drawID, fillColor)
|
||||
local lineColorMapped = {
|
||||
fillColor.r or 0,
|
||||
fillColor.g or 0,
|
||||
fillColor.b or 0,
|
||||
fillColor.a or 1
|
||||
}
|
||||
trigger.action.setMarkupColorFill(drawID, lineColorMapped)
|
||||
end
|
||||
|
||||
function DCS_UTIL.RemoveZoneDraw(drawID)
|
||||
if drawID ~= nil then
|
||||
trigger.action.removeMark(drawID)
|
||||
end
|
||||
end
|
||||
|
||||
--- spawns the units as specified in the mission file itself
|
||||
--- location and route can be nil and will then use default route
|
||||
---@param groupName string
|
||||
@@ -930,15 +989,15 @@ do -- INIT DCS_UTIL
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
---comment
|
||||
---@param groupId number
|
||||
---@return Group?
|
||||
---@return Group?
|
||||
function DCS_UTIL.GetPlayerGroupByGroupID(groupId)
|
||||
for i = 0,2 do
|
||||
for i = 0, 2 do
|
||||
local players = coalition.getPlayers(i)
|
||||
for key, unit in pairs(players) do
|
||||
if unit and unit:isExist() == true then
|
||||
|
||||
+65
-25
@@ -101,7 +101,7 @@ function Database.New(Logger)
|
||||
local zone_name = zone_data.name
|
||||
|
||||
---@type Vec2
|
||||
local zoneLocation = { x = zone_data.x, y = zone_data.z }
|
||||
local zoneLocation = { x = zone_data.location.x, y = zone_data.location.y }
|
||||
|
||||
local split_string = Spearhead.Util.split_string(zone_name, "_")
|
||||
table.insert(self._tables.AllZoneNames, zone_name)
|
||||
@@ -311,12 +311,9 @@ function Database.New(Logger)
|
||||
|
||||
|
||||
if zone.zone_type == Spearhead.DcsUtil.ZoneType.Cilinder then
|
||||
table.insert(capData.routes, { point1 = { x = zone.x, z = zone.z }, point2 = nil })
|
||||
table.insert(capData.routes, { point1 = { x = zone.location.x, z = zone.location.y, y = 0 }, point2 = nil })
|
||||
else
|
||||
local function getDist(a, b)
|
||||
return math.sqrt((b.x - a.x) ^ 2 + (b.z - a.z) ^ 2)
|
||||
end
|
||||
|
||||
|
||||
local biggest = nil
|
||||
local biggestA = nil
|
||||
local biggestB = nil
|
||||
@@ -325,7 +322,7 @@ function Database.New(Logger)
|
||||
for ii = i + 1, 4 do
|
||||
local a = zone.verts[i]
|
||||
local b = zone.verts[ii]
|
||||
local dist = getDist(a, b)
|
||||
local dist = Spearhead.Util.VectorDistance2d(a, b)
|
||||
|
||||
if biggest == nil or dist > biggest then
|
||||
biggestA = a
|
||||
@@ -338,8 +335,8 @@ function Database.New(Logger)
|
||||
if biggestA and biggestB then
|
||||
table.insert(capData.routes,
|
||||
{
|
||||
point1 = { x = biggestA.x, z = biggestA.z },
|
||||
point2 = { x = biggestB.x, z = biggestB.z }
|
||||
point1 = { x = biggestA.x, z = biggestA.y },
|
||||
point2 = { x = biggestB.x, z = biggestB.y }
|
||||
})
|
||||
end
|
||||
end
|
||||
@@ -348,9 +345,24 @@ function Database.New(Logger)
|
||||
end
|
||||
end
|
||||
|
||||
local totalUnits = 0
|
||||
local missions = 0
|
||||
for _, data in pairs(self._tables.MissionZoneData) do
|
||||
missions = missions + 1
|
||||
for _, groupName in pairs(data.Groups) do
|
||||
local group = Group.getByName(groupName)
|
||||
if group then
|
||||
totalUnits = totalUnits + group:getInitialSize()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if missions == 0 then missions = 1 end
|
||||
|
||||
self._logger:info("initiated the database with amount of zones: ")
|
||||
self._logger:info("Stages: " .. Spearhead.Util.tableLength(self._tables.StageZones))
|
||||
self._logger:info("Total Missions: " .. Spearhead.Util.tableLength(self._tables.MissionZoneData))
|
||||
self._logger:info("Average units per mission: " .. totalUnits / missions)
|
||||
self._logger:info("Random Missions: " .. Spearhead.Util.tableLength(self._tables.RandomMissionZones))
|
||||
self._logger:info("Farps: " .. Spearhead.Util.tableLength(self._tables.AllFarpZones))
|
||||
self._logger:info("Airbases: " .. Spearhead.Util.tableLength(self._tables.AirbaseDataPerAirfield))
|
||||
@@ -393,9 +405,21 @@ function Database:loadCapUnits()
|
||||
local all_groups = getAvailableCAPGroups()
|
||||
local airbases = world.getAirbases()
|
||||
for _, airbase in pairs(airbases) do
|
||||
local baseId = airbase:getID()
|
||||
local point = airbase:getPoint()
|
||||
local zone = Spearhead.DcsUtil.getAirbaseZoneById(baseId) or { x = point.x, z = point.z, radius = 4000 }
|
||||
|
||||
---@type SpearheadTriggerZone?
|
||||
local zone = Spearhead.DcsUtil.getAirbaseZoneByName(airbase:getName())
|
||||
|
||||
if zone == nil then
|
||||
zone = {
|
||||
location = {x = point.x, y = point.z} ,
|
||||
radius = 4000,
|
||||
name = "temp_zone",
|
||||
verts = {},
|
||||
zone_type = "Cilinder"
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
local baseData = self:getOrCreateAirbaseData(airbase:getName())
|
||||
local groups = Spearhead.DcsUtil.areGroupsInCustomZone(all_groups, zone)
|
||||
@@ -480,9 +504,19 @@ function Database:loadAirbaseGroups()
|
||||
|
||||
if base then
|
||||
local basedata = self:getOrCreateAirbaseData(baseName)
|
||||
local baseId = base:getID()
|
||||
local point = base:getPoint()
|
||||
local airbaseZone = Spearhead.DcsUtil.getAirbaseZoneById(baseId) or { x = point.x, z = point.z, radius = 4000 }
|
||||
local airbaseZone = Spearhead.DcsUtil.getAirbaseZoneByName(baseName)
|
||||
|
||||
if airbaseZone == nil then
|
||||
airbaseZone = {
|
||||
location = {x = point.x, y = point.z} ,
|
||||
radius = 4000,
|
||||
name = "temp_zone",
|
||||
verts = {},
|
||||
zone_type = "Cilinder"
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
if airbaseZone and base:getDesc().category == Airbase.Category.AIRDROME then
|
||||
local groups = Spearhead.DcsUtil.areGroupsInCustomZone(all_groups, airbaseZone)
|
||||
@@ -554,7 +588,11 @@ function Database:GetLocationForMissionZone(missionZoneName)
|
||||
return self._tables.MissionZonesLocations[missionZoneName]
|
||||
end
|
||||
|
||||
function Database:getCapRouteInZone(stageNumber, baseId)
|
||||
---comment
|
||||
---@param stageNumber string
|
||||
---@param baseName string
|
||||
---@return table?
|
||||
function Database:getCapRouteInZone(stageNumber, baseName)
|
||||
local stageNumber = tostring(stageNumber) or "nothing"
|
||||
local routeData = self._tables.CapDataPerStageNumber[stageNumber]
|
||||
if routeData then
|
||||
@@ -576,24 +614,24 @@ function Database:getCapRouteInZone(stageNumber, baseId)
|
||||
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 stageZoneName = Spearhead.Util.randomFromList(self._tables.StageZonesByNumber[stageNumber]) or "none"
|
||||
local stagezone = Spearhead.DcsUtil.getZoneByName(stageZoneName)
|
||||
if stagezone then
|
||||
local base = Spearhead.DcsUtil.getAirbaseById(baseId)
|
||||
|
||||
---@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.x, z = stagezone.z }, stagezone.radius,
|
||||
closest = GetClosestPointOnCircle({ x = stagezone.location.x, z = stagezone.location.y }, stagezone.radius,
|
||||
base:getPoint())
|
||||
else
|
||||
local function getDist(a, b)
|
||||
return math.sqrt((b.x - a.x) ^ 2 + (b.z - a.z) ^ 2)
|
||||
end
|
||||
|
||||
local closestDistance = -1
|
||||
for _, vert in pairs(stagezone.verts) do
|
||||
local distance = getDist(vert, base:getPoint())
|
||||
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
|
||||
@@ -602,17 +640,19 @@ function Database:getCapRouteInZone(stageNumber, baseId)
|
||||
end
|
||||
|
||||
if math.random(1, 2) % 2 == 0 then
|
||||
return { point1 = closest, point2 = { x = stagezone.x, z = stagezone.z } }
|
||||
return { point1 = closest, point2 = { x = stagezone.location.x, z = stagezone.location.y } }
|
||||
else
|
||||
return { point1 = { x = stagezone.x, z = stagezone.z }, point2 = closest }
|
||||
return { point1 = { x = stagezone.location.x, z = stagezone.location.y }, point2 = closest }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
---@return table result a list of stage zone names
|
||||
---@return Array<string> result a list of stage zone names
|
||||
function Database:getStagezoneNames()
|
||||
return self._tables.StageZoneNames
|
||||
end
|
||||
|
||||
@@ -73,7 +73,35 @@ do
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
---@class OnWeaponFiredListener
|
||||
---@field OnWeaponFired fun(self:OnWeaponFiredListener, unit:Unit?, weapon:Weapon?, target:Object?)
|
||||
|
||||
---@type Array<OnWeaponFiredListener>
|
||||
local onWeaponFiredListeners = {}
|
||||
|
||||
---comment
|
||||
---@param weaponFiredListener OnWeaponFiredListener
|
||||
SpearheadEvents.AddWeaponFiredListener = function(weaponFiredListener)
|
||||
if type(weaponFiredListener) ~= "table" then
|
||||
warn("Event handler not of type table/object")
|
||||
return
|
||||
end
|
||||
|
||||
table.insert(onWeaponFiredListeners, weaponFiredListener)
|
||||
end
|
||||
|
||||
local triggerWeaponFired = function(unit, weapon, target)
|
||||
for _, callable in pairs(onWeaponFiredListeners) do
|
||||
local succ, err = pcall(function()
|
||||
callable:OnWeaponFired(unit, weapon, target)
|
||||
end)
|
||||
|
||||
if err then
|
||||
logError(err)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local onLandEventListeners = {}
|
||||
---Add an event listener to a specific unit
|
||||
@@ -311,6 +339,15 @@ do
|
||||
|
||||
end
|
||||
|
||||
if event.id == world.event.S_EVENT_SHOT then
|
||||
|
||||
local shooter = event.initiator
|
||||
local weapon = event.weapon
|
||||
local target = event.target
|
||||
triggerWeaponFired(shooter, weapon, target)
|
||||
|
||||
end
|
||||
|
||||
if event.id == world.event.S_EVENT_MISSION_END then
|
||||
Spearhead.classes.persistence.Persistence.UpdateNow()
|
||||
end
|
||||
|
||||
@@ -212,12 +212,11 @@ do --setup route util
|
||||
---@param durationOnStation number
|
||||
---@param attackHelos boolean
|
||||
---@param deviationDistance number
|
||||
---@return table route
|
||||
ROUTE_UTIL.createCapMission = function(groupName, airdromeId, capPoint, racetrackSecondPoint, altitude, speed,
|
||||
durationOnStation, attackHelos, deviationDistance)
|
||||
---@return table? route
|
||||
ROUTE_UTIL.createCapMission = function(groupName, airdromeId, capPoint, racetrackSecondPoint, altitude, speed, durationOnStation, attackHelos, deviationDistance)
|
||||
local baseName = Spearhead.DcsUtil.getAirbaseName(airdromeId)
|
||||
if baseName == nil then
|
||||
return {}
|
||||
return nil
|
||||
end
|
||||
|
||||
durationOnStation = durationOnStation or 1800
|
||||
@@ -228,7 +227,7 @@ do --setup route util
|
||||
|
||||
local base = Airbase.getByName(baseName)
|
||||
if base == nil then
|
||||
return {}
|
||||
return nil
|
||||
end
|
||||
|
||||
local additionalFlyOverTasks = {
|
||||
|
||||
@@ -19,10 +19,11 @@ GlobalStageManager = {}
|
||||
---comment
|
||||
---@param database Database
|
||||
---@param stageConfig StageConfig
|
||||
---@param logLevel LogLevel
|
||||
---@return nil
|
||||
function GlobalStageManager:NewAndStart(database, stageConfig)
|
||||
local logger = Spearhead.LoggerTemplate.new("StageManager", stageConfig.logLevel)
|
||||
logger:info("Using Stage Log Level: " .. stageConfig.logLevel)
|
||||
function GlobalStageManager:NewAndStart(database, stageConfig, logLevel)
|
||||
local logger = Spearhead.LoggerTemplate.new("StageManager", logLevel)
|
||||
logger:info("Using Stage Log Level: " .. logLevel)
|
||||
local o = {}
|
||||
setmetatable(o, { __index = self })
|
||||
|
||||
@@ -122,7 +123,7 @@ function GlobalStageManager:NewAndStart(database, stageConfig)
|
||||
end
|
||||
|
||||
local stageDisplayName = split[3]
|
||||
local stagelogger = Spearhead.LoggerTemplate.new(stageName, stageConfig.logLevel)
|
||||
local stagelogger = Spearhead.LoggerTemplate.new(stageName, logLevel)
|
||||
if valid == true and orderNumber then
|
||||
|
||||
---@type StageInitData
|
||||
@@ -175,7 +176,7 @@ function GlobalStageManager:NewAndStart(database, stageConfig)
|
||||
end
|
||||
|
||||
if valid == true then
|
||||
local stagelogger = Spearhead.LoggerTemplate.new(stageName, stageConfig.logLevel)
|
||||
local stagelogger = Spearhead.LoggerTemplate.new(stageName, logLevel)
|
||||
|
||||
---@type WaitingStageInitData
|
||||
local initData = {
|
||||
|
||||
@@ -57,7 +57,6 @@ function BlueSam.New(database, logger, zoneName)
|
||||
for blueUnitName, blueUnitPos in pairs(blueUnitsPos) do
|
||||
for redUnitName, redUnitPos in pairs(redUnitsPos) do
|
||||
local distance = Spearhead.Util.VectorDistance3d(blueUnitPos, redUnitPos)
|
||||
env.info("distance: " .. tostring(distance))
|
||||
if distance <= cleanup_distance then
|
||||
self._cleanupUnits[redUnitName] = true
|
||||
end
|
||||
@@ -91,4 +90,4 @@ if Spearhead == nil then Spearhead = {} end
|
||||
if Spearhead.classes == nil then Spearhead.classes = {} end
|
||||
if Spearhead.classes.stageClasses == nil then Spearhead.classes.stageClasses = {} end
|
||||
if Spearhead.classes.stageClasses.SpecialZones == nil then Spearhead.classes.stageClasses.SpecialZones = {} end
|
||||
Spearhead.classes.stageClasses.Missions.SpecialZones = BlueSam
|
||||
Spearhead.classes.stageClasses.missions.SpecialZones = BlueSam
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
--- @class StageData
|
||||
--- @field missionsByCode table<string, Mission>
|
||||
--- @field missions Array<Mission>
|
||||
--- @field sams Array<Mission>
|
||||
--- @field missions Array<ZoneMission>
|
||||
--- @field sams Array<ZoneMission>
|
||||
--- @field blueSams Array<BlueSam>
|
||||
--- @field airbases Array<StageBase>
|
||||
--- @field miscGroups Array<SpearheadGroup>
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
--- @class Stage : MissionCompleteListener, OnStageChangedListener
|
||||
--- @field zoneName string
|
||||
--- @field stageName string
|
||||
--- @field stageName string?
|
||||
--- @field stageNumber number
|
||||
--- @field protected _isActive boolean
|
||||
--- @field protected _isComplete boolean
|
||||
@@ -47,6 +47,14 @@ Stage.__index = Stage
|
||||
|
||||
local stageDrawingId = 100
|
||||
|
||||
Stage.StageColors = {
|
||||
INVISIBLE = { r=0, g=0, b=0, a=0 },
|
||||
RED_ACTIVE = { r=1, g=0, b=0, a=0.15 },
|
||||
RED_PREACTIVE = { r=230/255, g=153/255, b=0, a=0.3},
|
||||
BLUE = { r=0, g=0, b=1, a=0.15},
|
||||
GRAY = { r=80/255, g=80/255, b=80/255, a=0.15 }
|
||||
}
|
||||
|
||||
---comment
|
||||
---@param database Database
|
||||
---@param stageConfig StageConfig
|
||||
@@ -83,7 +91,12 @@ function Stage:superNew(database, stageConfig, logger, initData, missionPriority
|
||||
self._activeStage = -99
|
||||
self._preActivated = false
|
||||
self._stageConfig = stageConfig or {}
|
||||
self._stageDrawingId = stageDrawingId + 1
|
||||
|
||||
local zone = Spearhead.DcsUtil.getZoneByName(self.zoneName)
|
||||
if zone then
|
||||
self._stageDrawingId = Spearhead.DcsUtil.DrawZone(zone, Stage.StageColors.INVISIBLE, Stage.StageColors.INVISIBLE, 4)
|
||||
end
|
||||
|
||||
self._spawnedGroups = {}
|
||||
self._missionPriority = missionPriority
|
||||
self._stageCompleteListeners = {}
|
||||
@@ -108,8 +121,9 @@ function Stage:superNew(database, stageConfig, logger, initData, missionPriority
|
||||
|
||||
do -- load tables
|
||||
local missionZones = database:getMissionsForStage(self.zoneName)
|
||||
self._logger:debug("Found " .. Spearhead.Util.tableLength(missionZones) .. " mission zones for stage: " .. self.zoneName)
|
||||
for _, missionZone in pairs(missionZones) do
|
||||
local mission = Spearhead.classes.stageClasses.Missions.Mission.New(missionZone, self._missionPriority, database, logger)
|
||||
local mission = Spearhead.classes.stageClasses.missions.ZoneMission.new(missionZone, self._missionPriority, database, logger)
|
||||
if mission then
|
||||
self._db.missionsByCode[mission.code] = mission
|
||||
if mission.missionType == "SAM" then
|
||||
@@ -125,7 +139,7 @@ function Stage:superNew(database, stageConfig, logger, initData, missionPriority
|
||||
---@type table<string, Array<Mission>>
|
||||
local randomMissionByName = {}
|
||||
for _, missionZoneName in pairs(randomMissionNames) do
|
||||
local mission = Spearhead.classes.stageClasses.Missions.Mission.New(missionZoneName, self._missionPriority, database, logger)
|
||||
local mission = Spearhead.classes.stageClasses.missions.ZoneMission.new(missionZoneName, self._missionPriority, database, logger)
|
||||
if mission then
|
||||
if randomMissionByName[mission.name] == nil then
|
||||
randomMissionByName[mission.name] = {}
|
||||
@@ -200,14 +214,14 @@ function Stage:IsComplete()
|
||||
if self._isComplete == true then return true end
|
||||
|
||||
for i, mission in pairs(self._db.sams) do
|
||||
local state = mission:GetState()
|
||||
local state = mission:getState()
|
||||
if state == "ACTIVE" or state == "NEW" then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
for i, mission in pairs(self._db.missions) do
|
||||
local state = mission:GetState()
|
||||
local state = mission:getState()
|
||||
if state == "ACTIVE" or state == "NEW" then
|
||||
return false
|
||||
end
|
||||
@@ -231,7 +245,7 @@ function Stage:CheckAndUpdateSelf()
|
||||
|
||||
local availableMissions = {}
|
||||
for _, mission in pairs(dbTables.missionsByCode) do
|
||||
local state = mission:GetState()
|
||||
local state = mission:getState()
|
||||
|
||||
if state == "ACTIVE" then
|
||||
activeCount = activeCount + 1
|
||||
@@ -266,7 +280,7 @@ end
|
||||
---private use only
|
||||
function Stage:NotifyComplete()
|
||||
|
||||
self._logger:info("Stage complete: " .. self.stageName)
|
||||
self._logger:info("Stage complete: " .. (self.stageName or self.stageNumber or "unknown"))
|
||||
|
||||
for _, listener in pairs(self._stageCompleteListeners) do
|
||||
pcall(function()
|
||||
@@ -285,12 +299,13 @@ function Stage:AddStageCompleteListener(listener)
|
||||
end
|
||||
|
||||
---Activates all SAMS, Airbase units etc all at once.
|
||||
function Stage:PreActivate()
|
||||
---@param draw boolean
|
||||
function Stage:PreActivate(draw)
|
||||
if self._preActivated == false then
|
||||
self._preActivated = true
|
||||
for key, mission in pairs(self._db.sams) do
|
||||
if mission then
|
||||
mission:SpawnActive()
|
||||
mission:SpawnInactive()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -298,36 +313,25 @@ function Stage:PreActivate()
|
||||
airbase:ActivateRedStage()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param stageColor StageColor
|
||||
function Stage:MarkStage(stageColor)
|
||||
local fillColor = {1, 0, 0, 0.1}
|
||||
local line ={ 1, 0,0, 1 }
|
||||
|
||||
if stageColor == "RED" then
|
||||
fillColor = {1, 0, 0, 0.1}
|
||||
line ={ 1, 0,0, 1 }
|
||||
elseif stageColor =="BLUE" then
|
||||
fillColor = {0, 0, 1, 0.1}
|
||||
line ={ 0, 0,1, 1 }
|
||||
elseif stageColor == "GRAY" then
|
||||
fillColor = {80/255, 80/255, 80/255, 0.15}
|
||||
line ={ 80/255, 80/255,80/255, 1 }
|
||||
if draw == true then
|
||||
self:MarkStage(Stage.StageColors.RED_PREACTIVE)
|
||||
end
|
||||
|
||||
local zone = Spearhead.DcsUtil.getZoneByName(self.zoneName)
|
||||
if zone and self._stageConfig.isDrawStagesEnabled == true then
|
||||
self._logger:debug("drawing stage: " .. self.zoneName)
|
||||
if zone.zone_type == Spearhead.DcsUtil.ZoneType.Cilinder then
|
||||
trigger.action.circleToAll(-1, self._stageDrawingId, {x = zone.x, y = 0 , z = zone.z}, zone.radius, {0,0,0,0}, {0,0,0,0},4, true)
|
||||
else
|
||||
--trigger.action.circleToAll(-1, self.stageDrawingId, {x = zone.x, y = 0 , z = zone.z}, zone.radius, { 1, 0,0, 1 }, {1,0,0,1},4, true)
|
||||
trigger.action.quadToAll( -1, self._stageDrawingId, zone.verts[1], zone.verts[2], zone.verts[3], zone.verts[4], {0,0,0,0}, {0,0,0,0}, 4, true)
|
||||
end
|
||||
end
|
||||
|
||||
trigger.action.setMarkupColorFill(self._stageDrawingId, fillColor)
|
||||
trigger.action.setMarkupColor(self._stageDrawingId, line)
|
||||
---@param stageColor DrawColor
|
||||
function Stage:MarkStage(stageColor)
|
||||
local lineColor = { r=stageColor.r, g=stageColor.g, b=stageColor.b, a=stageColor.a }
|
||||
local fillColor = { r=stageColor.r, g=stageColor.g, b=stageColor.b, a=stageColor.a }
|
||||
|
||||
if stageColor.a > 0 then
|
||||
lineColor.a = 1
|
||||
end
|
||||
|
||||
if self._stageDrawingId and self._stageConfig.isDrawStagesEnabled == true then
|
||||
Spearhead.DcsUtil.SetLineColor(self._stageDrawingId, lineColor)
|
||||
Spearhead.DcsUtil.SetFillColor(self._stageDrawingId, fillColor)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -335,10 +339,10 @@ function Stage:ActivateStage()
|
||||
self._isActive = true;
|
||||
|
||||
pcall(function()
|
||||
self:MarkStage("RED")
|
||||
self:MarkStage(Stage.StageColors.RED_ACTIVE)
|
||||
end)
|
||||
|
||||
self:PreActivate()
|
||||
self:PreActivate(false)
|
||||
|
||||
self._logger:debug("Activating Misc groups for zone. Count: " .. Spearhead.Util.tableLength(self._db.miscGroups))
|
||||
for _, miscGroup in pairs(self._db.miscGroups) do
|
||||
@@ -371,8 +375,12 @@ function Stage:OnStageNumberChanged(number)
|
||||
|
||||
local previousActive = self._activeStage
|
||||
self._activeStage = number
|
||||
if Spearhead.capInfo.IsCapActiveWhenZoneIsActive(self.zoneName, number) == true then
|
||||
self:PreActivate()
|
||||
|
||||
if self.stageNumber - self._activeStage == self._stageConfig.AmountPreactivateStage then
|
||||
self._logger:debug("Pre-activating stage: " .. self.zoneName .. " with number: " .. number)
|
||||
self:PreActivate(true)
|
||||
elseif Spearhead.capInfo.IsCapActiveWhenZoneIsActive(self.zoneName, number) == true then
|
||||
self:PreActivate(false)
|
||||
end
|
||||
|
||||
if number == self.stageNumber then
|
||||
@@ -434,7 +442,7 @@ function Stage:ActivateBlueStage()
|
||||
---@param self Stage
|
||||
local ActivateBlueAsync = function(self)
|
||||
pcall(function()
|
||||
self:MarkStage("BLUE")
|
||||
self:MarkStage(Stage.StageColors.BLUE)
|
||||
end)
|
||||
|
||||
self:ActivateBlueGroups()
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
|
||||
---@class ExtraStage : Stage
|
||||
local ExtraStage = {}
|
||||
|
||||
ExtraStage.__index = ExtraStage
|
||||
local Stage = Spearhead.classes.stageClasses.Stages.BaseStage.Stage
|
||||
setmetatable(ExtraStage, Stage)
|
||||
|
||||
|
||||
---comment
|
||||
---@param database Database
|
||||
@@ -14,11 +12,15 @@ setmetatable(ExtraStage, Stage)
|
||||
---@return ExtraStage
|
||||
function ExtraStage.New(database, stageConfig, logger, initData)
|
||||
|
||||
local Stage = Spearhead.classes.stageClasses.Stages.BaseStage.Stage
|
||||
setmetatable(ExtraStage, Stage)
|
||||
|
||||
local self = setmetatable({}, { __index = ExtraStage }) --[[@as ExtraStage]]
|
||||
self:superNew(database, stageConfig, logger, initData, "secondary")
|
||||
|
||||
self.OnPostBlueActivated = function (selfStage)
|
||||
selfStage:MarkStage("GRAY")
|
||||
|
||||
selfStage:MarkStage(Stage.StageColors.GRAY)
|
||||
end
|
||||
|
||||
self.OnPostStageComplete = function (selfStage)
|
||||
@@ -35,7 +37,7 @@ function ExtraStage:OnStageNumberChanged(number)
|
||||
|
||||
self._activeStage = number
|
||||
if Spearhead.capInfo.IsCapActiveWhenZoneIsActive(self.zoneName, number) == true then
|
||||
self:PreActivate()
|
||||
self:PreActivate(false)
|
||||
end
|
||||
|
||||
if number == self.stageNumber then
|
||||
|
||||
@@ -27,9 +27,10 @@ function WaitingStage.New(database, stageConfig, logger, initData)
|
||||
if initData.waitingSeconds and initData.waitingSeconds > 5 then self._waitTimeSeconds = initData.waitingSeconds end
|
||||
self._startTime = nil
|
||||
|
||||
self.CheckContinuousAsync = function (self, time)
|
||||
if self:IsComplete() then
|
||||
self:NotifyComplete()
|
||||
self.CheckContinuousAsync = function (selfA, time)
|
||||
|
||||
if selfA:IsComplete() == true then
|
||||
selfA:NotifyComplete()
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ function MissionCommandsHelper:AddOverviewCommand(groupID)
|
||||
end
|
||||
end
|
||||
|
||||
return string.format("[%s] %-15s %-20s %10s nM\n", mission.code, mission.displayMissionType, mission.name, distanceText)
|
||||
return string.format("[%s] %-15s %-20s %10s nM\n", mission.code, mission.missionTypeDisplay, mission.name, distanceText)
|
||||
end
|
||||
|
||||
---Primary missions
|
||||
@@ -218,7 +218,6 @@ function MissionCommandsHelper:addMissionCommands(groupId, mission)
|
||||
end
|
||||
|
||||
if path then
|
||||
self._logger:debug("Registering command: [" .. mission.code .. "]" .. mission.name)
|
||||
local missionFolderName = "[" .. mission.code .. "]" .. mission.name
|
||||
missionCommands.addSubMenuForGroup(groupId, missionFolderName, path)
|
||||
table.insert(path, missionFolderName)
|
||||
|
||||
@@ -0,0 +1,543 @@
|
||||
---@class RunwayStrikeMission : Mission
|
||||
---@field runwayBombingTracker RunwayBombingTracker
|
||||
---@field private _runway Runway
|
||||
---@field private _runwayZone SpearheadTriggerZone
|
||||
---@field private _airportName string
|
||||
---@field private _runwaySections Array<RunwaySection>
|
||||
---@field private _minKilosForDamage number
|
||||
---@field private _repairInProgress boolean
|
||||
local RunwayStrikeMission = {}
|
||||
|
||||
---@class RunwaySection
|
||||
---@field center Vec2
|
||||
---@field corners Array<Vec2>
|
||||
---@field kilosHit number
|
||||
---@field repairGroups Array<string>
|
||||
---@field drawID number?
|
||||
|
||||
|
||||
---@param runway Runway
|
||||
---@param database Database
|
||||
---@param logger Logger
|
||||
---@param runwayBombingTracker RunwayBombingTracker
|
||||
---@return RunwayStrikeMission?
|
||||
function RunwayStrikeMission.new(runway, airbaseName, database, logger, runwayBombingTracker)
|
||||
|
||||
local Mission = Spearhead.classes.stageClasses.missions.baseMissions.Mission
|
||||
RunwayStrikeMission.__index = RunwayStrikeMission
|
||||
setmetatable(RunwayStrikeMission, Mission)
|
||||
local self = setmetatable({}, RunwayStrikeMission)
|
||||
|
||||
self._airportName = airbaseName
|
||||
local missionBriefing = "Bomb runway " .. runway.Name .. " at " .. airbaseName .. "to delay the CAP effort"
|
||||
|
||||
local success, error = Mission.newSuper(self, "noZone", runway.Name, "OCA", missionBriefing, "secondary", database, logger)
|
||||
|
||||
self.runwayBombingTracker = runwayBombingTracker
|
||||
self._runway = runway
|
||||
self._runwayZone = self:RunwayToSpearheadZone(runway)
|
||||
self._repairInProgress = false
|
||||
self._minKilosForDamage = 100
|
||||
|
||||
local sections = self:ToSections(runway, 5)
|
||||
--[[
|
||||
+-----------+-----------+-----------+-----------+-----------+
|
||||
| Section 1 | Section 2 | Section 3 | Section 4 | Section 5 |
|
||||
+-----------+-----------+-----------+-----------+-----------+
|
||||
]]
|
||||
|
||||
self._runwaySections = {
|
||||
sections[2],
|
||||
sections[3],
|
||||
sections[4],
|
||||
} --only take 2, 3 and 4 cause those are the most imporant parts of the runway
|
||||
|
||||
if not success then
|
||||
logger:error("Failed to create RunwayBombingMission " .. runway.Name .. " => " .. error)
|
||||
return nil
|
||||
end
|
||||
|
||||
runwayBombingTracker:RegisterRunway(runway, self)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
---activates the mission
|
||||
function RunwayStrikeMission:SpawnActive()
|
||||
self._missionCommandsHelper:AddMissionToCommands(self)
|
||||
self:Draw()
|
||||
self:UpdateState()
|
||||
end
|
||||
|
||||
---@return SpearheadTriggerZone
|
||||
function RunwayStrikeMission:GetRunwayZone()
|
||||
return self._runwayZone
|
||||
end
|
||||
|
||||
function RunwayStrikeMission:RunwayHit(impactPoint, explosiveMass)
|
||||
|
||||
self._logger:debug("Runway hit: " .. self._airportName .. ":" .. self._runway.Name)
|
||||
for _, section in pairs(self._runwaySections) do
|
||||
|
||||
local zone = self:SectionToSpearheadZone(section)
|
||||
if Spearhead.Util.is3dPointInZone({ x = impactPoint.x, z = impactPoint.y, y = 0 }, zone) then
|
||||
if section.kilosHit == nil then
|
||||
section.kilosHit = 0
|
||||
end
|
||||
|
||||
section.kilosHit = section.kilosHit + explosiveMass
|
||||
end
|
||||
end
|
||||
|
||||
---@param selfA RunwayStrikeMission
|
||||
local updateState = function(selfA, time)
|
||||
selfA:UpdateState()
|
||||
end
|
||||
|
||||
timer.scheduleFunction(updateState, self, timer.getTime() + 5)
|
||||
|
||||
end
|
||||
|
||||
function RunwayStrikeMission:UpdateState()
|
||||
|
||||
self._logger:debug("Updating state of runway strike mission " .. self._airportName .. ":" .. self._runway.Name)
|
||||
|
||||
self:Draw()
|
||||
|
||||
for _, section in pairs(self._runwaySections) do
|
||||
if section.kilosHit > self._minKilosForDamage then
|
||||
self:StartRepair()
|
||||
self._missionCommandsHelper:RemoveMissionToCommands(self)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@type DrawColor
|
||||
local healthyAreaColor = { r=0, g=1, b=0, a=0.5 }
|
||||
---@type DrawColor
|
||||
local healthyAreaLineColor = { r=0, g=1, b=0, a=1 }
|
||||
---@type DrawColor
|
||||
local damagedAreaColor ={ r=1, g=165/255, b=0, a=0.5 }
|
||||
---@type DrawColor
|
||||
local damagedAreaLineColor = { r=1, g=165/255, b=0, a=1 }
|
||||
---@type DrawColor
|
||||
local destroyedAreaColor = { r=1, g=0, b=0, a=0.5 }
|
||||
---@type DrawColor
|
||||
local destroyedAreaLineColor = { r=1, g=0, b=0, a=1 }
|
||||
|
||||
---@private
|
||||
function RunwayStrikeMission:Draw()
|
||||
|
||||
---comment
|
||||
---@param runwaySection RunwaySection
|
||||
local function drawSection(runwaySection)
|
||||
local lineColor = healthyAreaLineColor
|
||||
local fillColor = healthyAreaColor
|
||||
|
||||
local orangeDamage = self._minKilosForDamage - 100
|
||||
if orangeDamage < 0 then orangeDamage = self._minKilosForDamage * 0.8 end
|
||||
|
||||
if runwaySection.kilosHit > self._minKilosForDamage then
|
||||
lineColor = destroyedAreaLineColor
|
||||
fillColor = destroyedAreaColor
|
||||
elseif runwaySection.kilosHit > orangeDamage then
|
||||
lineColor = damagedAreaLineColor
|
||||
fillColor = damagedAreaColor
|
||||
end
|
||||
|
||||
if runwaySection.drawID == nil then
|
||||
local zone = self:SectionToSpearheadZone(runwaySection)
|
||||
|
||||
local color = { r=0, g=1, b=0, a=0.5 }
|
||||
|
||||
runwaySection.drawID = Spearhead.DcsUtil.DrawZone(zone, color, color, 5)
|
||||
else
|
||||
Spearhead.DcsUtil.SetFillColor(runwaySection.drawID, fillColor)
|
||||
Spearhead.DcsUtil.SetLineColor(runwaySection.drawID, lineColor)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
for _, section in pairs(self._runwaySections) do
|
||||
drawSection(section)
|
||||
end
|
||||
end
|
||||
|
||||
---@private
|
||||
---@param section RunwaySection
|
||||
---@return SpearheadTriggerZone
|
||||
function RunwayStrikeMission:SectionToSpearheadZone(section)
|
||||
return {
|
||||
location = { x = self._runway.position.x, y = self._runway.position.z },
|
||||
radius = self._runway.width,
|
||||
name = self._runway.Name,
|
||||
verts = section.corners,
|
||||
zone_type = "Polygon",
|
||||
}
|
||||
end
|
||||
|
||||
---@
|
||||
|
||||
function RunwayStrikeMission:StartRepair()
|
||||
if self._repairInProgress == true then return end
|
||||
|
||||
self._repairInProgress = true
|
||||
self._logger:debug("Starting repair of runway strike mission " .. self._airportName .. ":" .. self._runway.Name)
|
||||
---comment
|
||||
---@param selfA any
|
||||
---@return unknown
|
||||
local repairTask = function (selfA, time)
|
||||
local interval = selfA:DoRepairCycle()
|
||||
if interval == nil then return nil end
|
||||
return time + interval
|
||||
end
|
||||
|
||||
timer.scheduleFunction(repairTask, self, timer.getTime() + 5)
|
||||
end
|
||||
|
||||
function RunwayStrikeMission:DoRepairCycle()
|
||||
|
||||
local interval = 5
|
||||
local repairPerSecond = 5
|
||||
|
||||
self._logger:debug("Repair cycle for" .. self._airportName .. ":" .. self._runway.Name)
|
||||
|
||||
local isHealed = true
|
||||
for _, section in pairs(self._runwaySections) do
|
||||
section.kilosHit = section.kilosHit - interval * repairPerSecond
|
||||
|
||||
if section.kilosHit > self._minKilosForDamage * 0.1 then
|
||||
self:AddOrUpdateRepairStatics(section)
|
||||
isHealed = false
|
||||
else
|
||||
self:RemoveRepairStatics(section)
|
||||
end
|
||||
end
|
||||
|
||||
self:Draw()
|
||||
|
||||
if isHealed == true then
|
||||
self._repairInProgress = false
|
||||
self:FullRepairRunway()
|
||||
self._logger:debug("Repair complete for runway strike mission " .. self._airportName .. ":" .. self._runway.Name)
|
||||
return nil
|
||||
end
|
||||
|
||||
return interval
|
||||
|
||||
end
|
||||
|
||||
---@class StaticSpawn
|
||||
---@field category string
|
||||
---@field type string
|
||||
---@field y number
|
||||
---@field x number
|
||||
---@field heading number
|
||||
|
||||
|
||||
local counter = 1
|
||||
|
||||
---@type Array<Array<StaticSpawn>>
|
||||
local repairStaticConfigs = {
|
||||
[1] = {
|
||||
[1] = {
|
||||
["category"] = "Unarmed",
|
||||
["type"] = "ZIL-135",
|
||||
["y"] = -4,
|
||||
["x"] = 10,
|
||||
["heading"] = 6.2133721370998,
|
||||
},
|
||||
[2] = {
|
||||
["category"] = "Unarmed",
|
||||
["type"] = "Tigr_233036",
|
||||
["y"] = 0,
|
||||
["x"] = 10,
|
||||
["heading"] = 6.2133721370998,
|
||||
},
|
||||
[3] = {
|
||||
["category"] = "Unarmed",
|
||||
["type"] = "Infantry AK ver3",
|
||||
["y"] = 10,
|
||||
["x"] = 7,
|
||||
["heading"] = 4.4331363000656,
|
||||
},
|
||||
[4] = {
|
||||
["category"] = "Unarmed",
|
||||
["type"] = "Infantry AK ver3",
|
||||
["y"] = 12,
|
||||
["x"] = 1,
|
||||
["heading"] = 4.4331363000656,
|
||||
},
|
||||
[5] = {
|
||||
["category"] = "Unarmed",
|
||||
["type"] = "Infantry AK ver3",
|
||||
["y"] = -10,
|
||||
["x"] = -2,
|
||||
["heading"] = 4.1538836197465,
|
||||
},
|
||||
[6] = {
|
||||
["category"] = "Unarmed",
|
||||
["type"] = "CV_59_Large_Forklift",
|
||||
["y"] = -11,
|
||||
["x"] = 0,
|
||||
["heading"] = 1.535889741755,
|
||||
},
|
||||
[7] = {
|
||||
["category"] = "Unarmed",
|
||||
["type"] = "ZiL-131 APA-80",
|
||||
["y"] = 11,
|
||||
["x"] = 5,
|
||||
["heading"] = 0.62831853071796,
|
||||
},
|
||||
[8] = {
|
||||
["category"] = "Unarmed",
|
||||
["type"] = "CV_59_NS60",
|
||||
["y"] = -4,
|
||||
["x"] = -15,
|
||||
["heading"] = 0.62831853071796,
|
||||
}
|
||||
},
|
||||
[2] = {
|
||||
[1] = {
|
||||
["category"] = "Air Defence",
|
||||
["type"] = "generator_5i57",
|
||||
["y"] = 2.7889551542015,
|
||||
["x"] = -4.7698247930386,
|
||||
["heading"] = 4.0666171571468,
|
||||
},
|
||||
[2] = {
|
||||
["category"] = "Infantry",
|
||||
["type"] = "Infantry AK ver3",
|
||||
["y"] = 3.639392285097,
|
||||
["x"] = 1.4300755972836,
|
||||
["heading"] = 5.0440015382636,
|
||||
},
|
||||
[3] = {
|
||||
["category"] = "Infantry",
|
||||
["type"] = "Infantry AK ver3",
|
||||
["y"] = 3.5139072826724,
|
||||
["x"] = -0.32671443666074,
|
||||
["heading"] = 6.16101225954,
|
||||
},
|
||||
[4] = {
|
||||
["category"] = "Unarmed",
|
||||
["type"] = "ATMZ-5",
|
||||
["y"] = -6.9556010010953,
|
||||
["x"] = 3.007681753102,
|
||||
["heading"] = 4.0666171571468,
|
||||
},
|
||||
[5] = {
|
||||
["category"] = "Unarmed",
|
||||
["type"] = "GAZ-66",
|
||||
["y"] = 5.9304017026008,
|
||||
["x"] = 0.80323129595153,
|
||||
["heading"] = 6.2308254296198,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
---@private
|
||||
---@param section RunwaySection
|
||||
function RunwayStrikeMission:AddOrUpdateRepairStatics(section)
|
||||
if Spearhead.Util.tableLength(section.repairGroups) > 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local location = { --Can randomise a little later
|
||||
x = section.center.x,
|
||||
y = section.center.y,
|
||||
}
|
||||
|
||||
local repairGroup = Spearhead.Util.randomFromList(repairStaticConfigs)
|
||||
for _, repairStatic in pairs(repairGroup) do
|
||||
|
||||
repairStatic.x = location.x + repairStatic.x
|
||||
repairStatic.y = location.y + repairStatic.y
|
||||
|
||||
repairStatic.hidden = true
|
||||
repairStatic.name = "runway_repairunit_" .. counter
|
||||
counter = counter + 1
|
||||
coalition.addStaticObject(country.id.RUSSIA, repairStatic)
|
||||
table.insert(section.repairGroups, repairStatic.name)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
---@private
|
||||
---@param section RunwaySection
|
||||
function RunwayStrikeMission:RemoveRepairStatics(section)
|
||||
if Spearhead.Util.tableLength(section.repairGroups) == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
for _, repairGroup in pairs(section.repairGroups) do
|
||||
local static = StaticObject.getByName(repairGroup)
|
||||
if static then
|
||||
static:destroy()
|
||||
end
|
||||
end
|
||||
|
||||
section.repairGroups = {}
|
||||
end
|
||||
|
||||
---@private
|
||||
function RunwayStrikeMission:FullRepairRunway()
|
||||
|
||||
for _, section in pairs(self._runwaySections) do
|
||||
section.kilosHit = 0
|
||||
self:RemoveRepairStatics(section)
|
||||
end
|
||||
|
||||
local minX = self._runwayZone.verts[1].x
|
||||
local minY = self._runwayZone.verts[1].y
|
||||
local maxX = self._runwayZone.verts[1].x
|
||||
local maxY = self._runwayZone.verts[1].y
|
||||
|
||||
for _, vert in pairs(self._runwayZone.verts) do
|
||||
if vert.x < minX then
|
||||
minX = vert.x
|
||||
end
|
||||
if vert.x > maxX then
|
||||
maxX = vert.x
|
||||
end
|
||||
if vert.y < minY then
|
||||
minY = vert.y
|
||||
end
|
||||
if vert.y > maxY then
|
||||
maxY = vert.y
|
||||
end
|
||||
end
|
||||
|
||||
---@type Box
|
||||
local box = {
|
||||
id = world.VolumeType.BOX,
|
||||
params = {
|
||||
min = { x = minX, z = minY, y = 0 },
|
||||
max = { x = maxX, z = maxY, y = 10000 },
|
||||
}
|
||||
}
|
||||
world.removeJunk(box)
|
||||
end
|
||||
|
||||
---@private
|
||||
---@param runway Runway
|
||||
---@return Array<RunwaySection>
|
||||
function RunwayStrikeMission:ToSections(runway, numSections)
|
||||
|
||||
local sections = {}
|
||||
local center = runway.position
|
||||
local heading = runway.course
|
||||
local width = runway.width
|
||||
local length = runway.length / numSections
|
||||
|
||||
if heading < 0 then
|
||||
heading = math.abs(heading)
|
||||
else
|
||||
heading = 0 - heading
|
||||
end
|
||||
|
||||
local cosH = math.cos(heading)
|
||||
local sinH = math.sin(heading)
|
||||
|
||||
for i = 0, numSections - 1 do
|
||||
local sectionCenterOffset = (i - (numSections / 2) + 0.5) * length
|
||||
|
||||
---@type Vec2
|
||||
local sectionCenter = {
|
||||
x = center.x + sectionCenterOffset * cosH,
|
||||
y = center.z + sectionCenterOffset * sinH
|
||||
}
|
||||
|
||||
local halfWidth = width / 2
|
||||
local halfLength = length / 2
|
||||
|
||||
---@type Array<Vec2>
|
||||
local corners = {
|
||||
{
|
||||
x = sectionCenter.x + (-halfLength * cosH - halfWidth * sinH),
|
||||
y = sectionCenter.y + (-halfLength * sinH + halfWidth * cosH)
|
||||
},
|
||||
{
|
||||
x = sectionCenter.x + (-halfLength * cosH + halfWidth * sinH),
|
||||
y = sectionCenter.y + (-halfLength * sinH - halfWidth * cosH)
|
||||
},
|
||||
{
|
||||
x = sectionCenter.x + (halfLength * cosH + halfWidth * sinH),
|
||||
y = sectionCenter.y + (halfLength * sinH - halfWidth * cosH)
|
||||
},
|
||||
{
|
||||
x = sectionCenter.x + (halfLength * cosH - halfWidth * sinH),
|
||||
y = sectionCenter.y + (halfLength * sinH + halfWidth * cosH)
|
||||
}
|
||||
}
|
||||
|
||||
---@type RunwaySection
|
||||
local section = {
|
||||
center = sectionCenter,
|
||||
corners = corners,
|
||||
kilosHit = 0,
|
||||
repairGroups = {},
|
||||
}
|
||||
|
||||
table.insert(sections, section)
|
||||
end
|
||||
|
||||
return sections
|
||||
end
|
||||
|
||||
---@private
|
||||
---@param runway Runway
|
||||
---@return SpearheadTriggerZone
|
||||
function RunwayStrikeMission:RunwayToSpearheadZone(runway)
|
||||
|
||||
-- Calculate the 4 corner points of the runway based on heading, height, and width
|
||||
local radHeading = runway.course
|
||||
|
||||
if radHeading < 0 then
|
||||
radHeading = math.abs(radHeading)
|
||||
else
|
||||
radHeading = 0 - radHeading
|
||||
end
|
||||
|
||||
local cosH = math.cos(radHeading)
|
||||
local sinH = math.sin(radHeading)
|
||||
|
||||
local halfWidth = runway.width / 2
|
||||
local halfHeight = runway.length / 2
|
||||
|
||||
---@type Array<Vec2>
|
||||
local corners = {
|
||||
{
|
||||
x = runway.position.x + (-halfHeight * cosH - halfWidth * sinH),
|
||||
y = runway.position.z + (-halfHeight * sinH + halfWidth * cosH)
|
||||
},
|
||||
{
|
||||
x = runway.position.x + (-halfHeight * cosH + halfWidth * sinH),
|
||||
y = runway.position.z + (-halfHeight * sinH - halfWidth * cosH)
|
||||
},
|
||||
{
|
||||
x = runway.position.x + (halfHeight * cosH + halfWidth * sinH),
|
||||
y = runway.position.z + (halfHeight * sinH - halfWidth * cosH)
|
||||
},
|
||||
{
|
||||
x = runway.position.x + (halfHeight * cosH - halfWidth * sinH),
|
||||
y = runway.position.z + (halfHeight * sinH + halfWidth * cosH)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
location = { x = runway.position.x, y = runway.position.z },
|
||||
radius = runway.width,
|
||||
name = runway.Name,
|
||||
verts = corners,
|
||||
zone_type = "Polygon",
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
|
||||
if not Spearhead.classes then Spearhead.classes = {} end
|
||||
if not Spearhead.classes.stageClasses then Spearhead.classes.stageClasses = {} end
|
||||
if not Spearhead.classes.stageClasses.missions then Spearhead.classes.stageClasses.missions = {} end
|
||||
Spearhead.classes.stageClasses.missions.RunwayStrikeMission = RunwayStrikeMission
|
||||
+161
-209
@@ -1,32 +1,55 @@
|
||||
|
||||
|
||||
---@class Mission : OnUnitLostListener
|
||||
---@field name string
|
||||
---@field missionType missionType
|
||||
---@field displayMissionType string
|
||||
---@field code string
|
||||
---@field priority MissionPriority
|
||||
---@field location Vec2?
|
||||
---@field zoneName string
|
||||
---@field private _state MissionState
|
||||
---@field private _database Database
|
||||
---@field private _logger Logger
|
||||
---@field private _missionBriefing string?
|
||||
--- ZoneMission is missions that are defined by zones in the ME
|
||||
---@class ZoneMission : Mission, OnUnitLostListener
|
||||
---@field private _missionGroups MissionGroups
|
||||
---@field private _completeListeners Array<MissionCompleteListener>
|
||||
---@field private _missionCommandsHelper MissionCommandsHelper
|
||||
local Mission = {}
|
||||
local ZoneMission = {}
|
||||
|
||||
--- @class MissionCompleteListener
|
||||
--- @field OnMissionComplete fun(self: any, mission:Mission)
|
||||
|
||||
--- @class MissionGroups
|
||||
--- @class MissionGroups
|
||||
--- @field hasTargets boolean
|
||||
--- @field groups Array<SpearheadGroup>
|
||||
--- @field unitsAlive table<string, table<string, boolean>>
|
||||
--- @field targetsAlive table<string, table<string, boolean>>
|
||||
--- @field groupNamesPerunit table<string,string>
|
||||
|
||||
---@class ParsedMissionName
|
||||
---@field missionName string
|
||||
---@field type MissionType
|
||||
|
||||
---comment
|
||||
---@param input string
|
||||
---@return ParsedMissionName?
|
||||
local function ParseZoneName(input)
|
||||
local split_name = Spearhead.Util.split_string(input, "_")
|
||||
local split_length = Spearhead.Util.tableLength(split_name)
|
||||
if Spearhead.Util.startswith(input, "RANDOMMISSION") == true and split_length < 4 then
|
||||
Spearhead.AddMissionEditorWarning("Random Mission with zonename " .. input .. " not in right format")
|
||||
return nil
|
||||
elseif split_length < 3 then
|
||||
Spearhead.AddMissionEditorWarning("Mission with zonename" .. input .. " not in right format")
|
||||
return nil
|
||||
end
|
||||
|
||||
---@type MissionType
|
||||
local parsedType = "nil"
|
||||
|
||||
local inputType = string.lower(split_name[2])
|
||||
if inputType == "dead" then parsedType = "DEAD" end
|
||||
if inputType == "strike" then parsedType = "STRIKE" end
|
||||
if inputType == "bai" then parsedType = "BAI" end
|
||||
if inputType == "sam" then parsedType = "SAM" end
|
||||
|
||||
if parsedType == "nil" then
|
||||
Spearhead.AddMissionEditorWarning("Mission with zonename '" .. input .. "' has an unsupported type '" .. (type or "nil" ))
|
||||
return nil
|
||||
end
|
||||
local name = split_name[3]
|
||||
return {
|
||||
missionName = name,
|
||||
type = parsedType
|
||||
}
|
||||
end
|
||||
|
||||
MINIMAL_UNITS_ALIVE_RATIO = 0.21
|
||||
|
||||
---comment
|
||||
@@ -34,64 +57,32 @@ MINIMAL_UNITS_ALIVE_RATIO = 0.21
|
||||
---@param priority MissionPriority
|
||||
---@param database Database
|
||||
---@param logger Logger
|
||||
---@return Mission?
|
||||
function Mission.New(zoneName, priority, database, logger)
|
||||
---@return ZoneMission?
|
||||
function ZoneMission.new(zoneName, priority, database, logger)
|
||||
local Mission = Spearhead.classes.stageClasses.missions.baseMissions.Mission
|
||||
ZoneMission.__index = ZoneMission
|
||||
setmetatable(ZoneMission, Mission)
|
||||
|
||||
local function ParseZoneName(input)
|
||||
local split_name = Spearhead.Util.split_string(input, "_")
|
||||
local split_length = Spearhead.Util.tableLength(split_name)
|
||||
if Spearhead.Util.startswith(input, "RANDOMMISSION") == true and split_length < 4 then
|
||||
Spearhead.AddMissionEditorWarning("Random Mission with zonename " .. input .. " not in right format")
|
||||
return nil
|
||||
elseif split_length < 3 then
|
||||
Spearhead.AddMissionEditorWarning("Mission with zonename" .. input .. " not in right format")
|
||||
return nil
|
||||
end
|
||||
|
||||
---@type missionType
|
||||
local parsedType = "nil"
|
||||
|
||||
local inputType = string.lower(split_name[2])
|
||||
if inputType == "dead" then parsedType = "DEAD" end
|
||||
if inputType == "strike" then parsedType = "STRIKE" end
|
||||
if inputType == "bai" then parsedType = "BAI" end
|
||||
if inputType == "sam" then parsedType = "SAM" end
|
||||
|
||||
if parsedType == "nil" then
|
||||
Spearhead.AddMissionEditorWarning("Mission with zonename '" .. input .. "' has an unsupported type '" .. (type or "nil" ))
|
||||
return nil
|
||||
end
|
||||
local name = split_name[3]
|
||||
return {
|
||||
missionName = name,
|
||||
type = parsedType
|
||||
}
|
||||
end
|
||||
local self = setmetatable({}, ZoneMission)
|
||||
|
||||
local parsed = ParseZoneName(zoneName)
|
||||
if not parsed then
|
||||
logger:error("Failed to create ZoneMission " .. zoneName .. " => invalid name")
|
||||
return nil
|
||||
end
|
||||
|
||||
if parsed == nil then return end
|
||||
local missionBriefing = database:getMissionBriefingForMissionZone(zoneName) or "no briefing provided"
|
||||
|
||||
Mission.__index = Mission
|
||||
local o = {}
|
||||
local self = setmetatable(o, Mission)
|
||||
|
||||
self.zoneName = zoneName
|
||||
self.name = parsed.missionName
|
||||
self.missionType = parsed.type
|
||||
self.displayMissionType = self.missionType or "unknown"
|
||||
if self.missionType == "SAM" then self.displayMissionType = "DEAD" end
|
||||
self.location = database:GetLocationForMissionZone(zoneName)
|
||||
self.code = tostring(database:GetNewMissionCode())
|
||||
self.priority = priority
|
||||
self._state = "NEW"
|
||||
local success, error = Mission.newSuper(self, zoneName, parsed.missionName, parsed.type, missionBriefing, priority, database, logger)
|
||||
if not success then
|
||||
logger:error("Failed to create ZoneMission " .. zoneName .. " => " .. error)
|
||||
return nil
|
||||
end
|
||||
|
||||
self._logger = logger
|
||||
self._database = database
|
||||
self._missionCommandsHelper = Spearhead.classes.stageClasses.helpers.MissionCommandsHelper.getOrCreate(logger.LogLevel)
|
||||
self._completeListeners = {}
|
||||
if self.missionType == "SAM" then
|
||||
self.missionTypeDisplay = "DEAD"
|
||||
end
|
||||
|
||||
self._missionBriefing = database:getMissionBriefingForMissionZone(zoneName)
|
||||
self._missionGroups = {
|
||||
groups = {},
|
||||
unitsAlive = {},
|
||||
@@ -103,16 +94,15 @@ function Mission.New(zoneName, priority, database, logger)
|
||||
local SpearheadGroup = Spearhead.classes.stageClasses.Groups.SpearheadGroup
|
||||
local groupNames = database:getGroupsForMissionZone(zoneName)
|
||||
for _, groupName in pairs(groupNames) do
|
||||
|
||||
local spearheadGroup = SpearheadGroup.New(groupName)
|
||||
table.insert(self._missionGroups.groups, spearheadGroup)
|
||||
|
||||
local isGroupTarget =Spearhead.Util.startswith(string.lower(groupName), "tgt_")
|
||||
for _, unit in pairs(spearheadGroup:GetUnits())do
|
||||
local isGroupTarget = Spearhead.Util.startswith(string.lower(groupName), "tgt_")
|
||||
for _, unit in pairs(spearheadGroup:GetUnits()) do
|
||||
local unitName = unit:getName()
|
||||
local isUnitTarget = Spearhead.Util.startswith(string.lower(unitName), "tgt_")
|
||||
|
||||
if self._missionGroups.unitsAlive[groupName] == nil then
|
||||
if self._missionGroups.unitsAlive[groupName] == nil then
|
||||
self._missionGroups.unitsAlive[groupName] = {}
|
||||
end
|
||||
|
||||
@@ -135,109 +125,15 @@ function Mission.New(zoneName, priority, database, logger)
|
||||
Spearhead.DcsUtil.DestroyGroup(groupName)
|
||||
end
|
||||
|
||||
self._logger:debug("Mission " .. self.name .. " group count: " .. Spearhead.Util.tableLength(groupNames))
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
---comment
|
||||
---@return MissionState
|
||||
function Mission:GetState()
|
||||
return self._state
|
||||
end
|
||||
|
||||
function Mission:SpawnPersistedState()
|
||||
for _, group in pairs(self._missionGroups.groups) do
|
||||
group:SpawnCorpsesOnly()
|
||||
end
|
||||
end
|
||||
|
||||
function Mission:SpawnActive()
|
||||
|
||||
self._logger:info("Activating " .. self.name)
|
||||
|
||||
self._state = "ACTIVE"
|
||||
for _, group in pairs(self._missionGroups.groups) do
|
||||
group:Spawn()
|
||||
end
|
||||
|
||||
self._missionCommandsHelper:AddMissionToCommands(self)
|
||||
|
||||
self:StartCheckingContinuous()
|
||||
end
|
||||
|
||||
---@private
|
||||
function Mission:StartCheckingContinuous()
|
||||
---comment
|
||||
---@param mission Mission
|
||||
---@param time any
|
||||
---@return unknown
|
||||
local Check = function (mission, time)
|
||||
mission:UpdateState(true, true)
|
||||
|
||||
if mission:GetState() == "COMPLETED" then
|
||||
return nil
|
||||
end
|
||||
return time + 30
|
||||
end
|
||||
timer.scheduleFunction(Check, self, timer.getTime() + 30)
|
||||
end
|
||||
|
||||
---@private
|
||||
---@return string?
|
||||
function Mission:ToStateString()
|
||||
if self._missionGroups.hasTargets == true then
|
||||
local dead = 0
|
||||
local total = 0
|
||||
if self._missionGroups.targetsAlive then
|
||||
for _, group in pairs(self._missionGroups.targetsAlive) do
|
||||
for _, isAlive in pairs(group) do
|
||||
total = total + 1
|
||||
if isAlive == false then
|
||||
dead = dead + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if total > 0 then
|
||||
local completionPercentage = math.floor((dead / total) * 100)
|
||||
return "Targets Destroyed: " .. completionPercentage .. "%"
|
||||
end
|
||||
else
|
||||
local dead = 0
|
||||
local total = 0
|
||||
if self._missionGroups.unitsAlive then
|
||||
for _, group in pairs(self._missionGroups.unitsAlive) do
|
||||
for _, isAlive in pairs(group) do
|
||||
total = total + 1
|
||||
if isAlive == false then
|
||||
dead = dead + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if total > 0 then
|
||||
local completionPercentage = math.floor((dead / total) * 100)
|
||||
return "Units Destroyed: " .. completionPercentage .. "%"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---comment
|
||||
---@param groupId integer
|
||||
function Mission:ShowBriefing(groupId)
|
||||
|
||||
local stateString = self:ToStateString()
|
||||
|
||||
if self._missionBriefing == nil or self._missionBriefing == "" then self._missionBriefing = "No briefing available" end
|
||||
local text = "Mission [" .. self.code .. "] ".. self.name .. "\n \n" .. self._missionBriefing .. " \n \n" .. stateString
|
||||
trigger.action.outTextForGroup(groupId, text, 30);
|
||||
end
|
||||
|
||||
|
||||
---@internal
|
||||
---@param checkHealth boolean
|
||||
---@param messageIfDone boolean
|
||||
function Mission:UpdateState(checkHealth, messageIfDone)
|
||||
function ZoneMission:UpdateState(checkHealth, messageIfDone)
|
||||
if checkHealth == nil then checkHealth = false end
|
||||
if messageIfDone == false then messageIfDone = true end
|
||||
|
||||
@@ -294,7 +190,6 @@ function Mission:UpdateState(checkHealth, messageIfDone)
|
||||
end
|
||||
|
||||
if self._missionGroups.hasTargets == true then
|
||||
|
||||
local anyTargetAlive = function()
|
||||
for _, units in pairs(self._missionGroups.targetsAlive) do
|
||||
for _, isAlive in pairs(units) do
|
||||
@@ -305,7 +200,7 @@ function Mission:UpdateState(checkHealth, messageIfDone)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
if anyTargetAlive() ~= true then
|
||||
self._state = "COMPLETED"
|
||||
end
|
||||
@@ -337,44 +232,101 @@ function Mission:UpdateState(checkHealth, messageIfDone)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function ZoneMission:SpawnPersistedState()
|
||||
for _, group in pairs(self._missionGroups.groups) do
|
||||
group:SpawnCorpsesOnly()
|
||||
end
|
||||
end
|
||||
|
||||
---spawns the mission, but doesn't add
|
||||
function ZoneMission:SpawnInactive()
|
||||
self._logger:info("PreActivating " .. self.name)
|
||||
|
||||
self._state = "ACTIVE"
|
||||
for _, group in pairs(self._missionGroups.groups) do
|
||||
group:Spawn()
|
||||
end
|
||||
end
|
||||
|
||||
function ZoneMission:SpawnActive()
|
||||
|
||||
self._logger:info("Activating " .. self.name)
|
||||
|
||||
self._state = "ACTIVE"
|
||||
for _, group in pairs(self._missionGroups.groups) do
|
||||
group:Spawn()
|
||||
end
|
||||
|
||||
self._missionCommandsHelper:AddMissionToCommands(self)
|
||||
|
||||
self:StartCheckingContinuous()
|
||||
end
|
||||
|
||||
|
||||
---@private
|
||||
function ZoneMission:StartCheckingContinuous()
|
||||
---comment
|
||||
---@param mission Mission
|
||||
local NotifyMissionComplete = function(mission)
|
||||
mission:NotifyMissionComplete()
|
||||
return nil
|
||||
---@param time any
|
||||
---@return unknown
|
||||
local Check = function(mission, time)
|
||||
mission:UpdateState(true, true)
|
||||
|
||||
if mission:getState() == "COMPLETED" then
|
||||
return nil
|
||||
end
|
||||
return time + 30
|
||||
end
|
||||
if self._state == "COMPLETED" then
|
||||
timer.scheduleFunction(NotifyMissionComplete, self, timer.getTime() + 3)
|
||||
timer.scheduleFunction(Check, self, timer.getTime() + 30)
|
||||
end
|
||||
|
||||
---@protected
|
||||
function ZoneMission:ToStateString()
|
||||
if self._missionGroups.hasTargets == true then
|
||||
local dead = 0
|
||||
local total = 0
|
||||
if self._missionGroups.targetsAlive then
|
||||
for _, group in pairs(self._missionGroups.targetsAlive) do
|
||||
for _, isAlive in pairs(group) do
|
||||
total = total + 1
|
||||
if isAlive == false then
|
||||
dead = dead + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if total > 0 then
|
||||
local completionPercentage = math.floor((dead / total) * 100)
|
||||
return "Targets Destroyed: " .. completionPercentage .. "%"
|
||||
end
|
||||
else
|
||||
local dead = 0
|
||||
local total = 0
|
||||
if self._missionGroups.unitsAlive then
|
||||
for _, group in pairs(self._missionGroups.unitsAlive) do
|
||||
for _, isAlive in pairs(group) do
|
||||
total = total + 1
|
||||
if isAlive == false then
|
||||
dead = dead + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if total > 0 then
|
||||
local completionPercentage = math.floor((dead / total) * 100)
|
||||
return "Units Destroyed: " .. completionPercentage .. "%"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---private usage advised
|
||||
function Mission:NotifyMissionComplete()
|
||||
|
||||
self._missionCommandsHelper:RemoveMissionToCommands(self)
|
||||
self._logger:info("Mission Completed: " .. self.zoneName)
|
||||
trigger.action.outText("Mission " .. self.name .. " [" .. self.code .. "] was completed succesfully" , 20)
|
||||
|
||||
for _, listener in pairs(self._completeListeners) do
|
||||
pcall(function()
|
||||
listener:OnMissionComplete(self)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
---@param listener MissionCompleteListener Object that implements "OnMissionComplete(self, mission)"
|
||||
function Mission:AddMissionCompleteListener(listener)
|
||||
if type(listener) ~= "table" then
|
||||
return
|
||||
end
|
||||
table.insert(self._completeListeners, listener)
|
||||
end
|
||||
|
||||
function Mission:OnUnitLost(object)
|
||||
function ZoneMission:OnUnitLost(object)
|
||||
--[[
|
||||
OnUnit lost event
|
||||
]]--
|
||||
]] --
|
||||
self._logger:debug("Getting on unit lost event")
|
||||
|
||||
local category = Object.getCategory(object)
|
||||
@@ -388,7 +340,7 @@ function Mission:OnUnitLost(object)
|
||||
if self._missionGroups.targetsAlive[groupName] and self._missionGroups.targetsAlive[groupName][unitName] then
|
||||
self._missionGroups.targetsAlive[groupName][unitName] = false
|
||||
end
|
||||
elseif category == Object.Category.STATIC then
|
||||
elseif category == Object.Category.STATIC then
|
||||
local name = object:getName()
|
||||
self._missionGroups.unitsAlive[name][name] = false
|
||||
|
||||
@@ -401,9 +353,9 @@ function Mission:OnUnitLost(object)
|
||||
self:UpdateState(false, true)
|
||||
end
|
||||
|
||||
|
||||
|
||||
if not Spearhead.classes then Spearhead.classes = {} end
|
||||
if not Spearhead.classes.stageClasses then Spearhead.classes.stageClasses = {} end
|
||||
if not Spearhead.classes.stageClasses.Missions then Spearhead.classes.stageClasses.Missions = {} end
|
||||
Spearhead.classes.stageClasses.Missions.Mission = Mission
|
||||
|
||||
|
||||
if not Spearhead.classes.stageClasses.missions then Spearhead.classes.stageClasses.missions = {} end
|
||||
Spearhead.classes.stageClasses.missions.ZoneMission = ZoneMission
|
||||
@@ -0,0 +1,147 @@
|
||||
|
||||
|
||||
---@class Mission
|
||||
---@field name string
|
||||
---@field zoneName string
|
||||
---@field missionType MissionType
|
||||
---@field missionTypeDisplay string
|
||||
---@field priority MissionPriority
|
||||
---@field missionBriefing string
|
||||
---@field location Vec2?
|
||||
---@field code string
|
||||
---@field protected state MissionState
|
||||
---@field getState fun(self: Mission): MissionState @Get the mission state
|
||||
---@field protected _logger Logger
|
||||
---@field protected _database Database
|
||||
---@field protected _missionCommandsHelper MissionCommandsHelper
|
||||
---@field protected _completeListeners Array<MissionCompleteListener>
|
||||
local Mission = {}
|
||||
Mission.__index = Mission
|
||||
|
||||
--- @class MissionCompleteListener
|
||||
--- @field OnMissionComplete fun(self: any, mission:Mission)
|
||||
|
||||
---@protected
|
||||
---@param self Mission
|
||||
---@param zoneName string
|
||||
---@param missionName string
|
||||
---@param missionType MissionType
|
||||
---@param missionBriefing string
|
||||
---@param priority MissionPriority
|
||||
---@param database Database
|
||||
---@param logger Logger
|
||||
---@return boolean, string
|
||||
function Mission.newSuper(self, zoneName, missionName, missionType, missionBriefing, priority, database, logger)
|
||||
|
||||
self.zoneName = zoneName
|
||||
self.name = missionName
|
||||
self.missionType = missionType
|
||||
self.priority = priority
|
||||
self.state = "NEW"
|
||||
self._logger = logger
|
||||
self._database = database
|
||||
self.missionBriefing = missionBriefing
|
||||
self.code = tostring(database:GetNewMissionCode())
|
||||
|
||||
self._completeListeners = {}
|
||||
|
||||
self.location = database:GetLocationForMissionZone(zoneName)
|
||||
self.missionTypeDisplay = self.missionType
|
||||
|
||||
self._missionCommandsHelper = Spearhead.classes.stageClasses.helpers.MissionCommandsHelper.getOrCreate(logger.LogLevel)
|
||||
|
||||
return true, "success"
|
||||
end
|
||||
|
||||
---@return MissionState
|
||||
function Mission:getState()
|
||||
return self.state
|
||||
end
|
||||
|
||||
--region PUBLIC
|
||||
|
||||
function Mission:SpawnPersistedState() end
|
||||
function Mission:SpawnActive() end
|
||||
|
||||
---comment
|
||||
---@param checkHealth boolean
|
||||
---@param messageIfDone boolean
|
||||
function Mission:UpdateState(checkHealth, messageIfDone) end
|
||||
function Mission:StartCheckingContinuous() end
|
||||
|
||||
|
||||
---comment
|
||||
---@param groupId number
|
||||
function Mission:ShowBriefing(groupId)
|
||||
local stateString = self:ToStateString()
|
||||
if self._missionBriefing == nil or self._missionBriefing == "" then self._missionBriefing = "No briefing available" end
|
||||
local text = "Mission [" ..
|
||||
self.code .. "] " .. self.name .. "\n \n" .. self._missionBriefing .. " \n \n" .. stateString
|
||||
trigger.action.outTextForGroup(groupId, text, 30);
|
||||
end
|
||||
|
||||
|
||||
---@param listener MissionCompleteListener Object that implements "OnMissionComplete(self, mission)"
|
||||
function Mission:AddMissionCompleteListener(listener)
|
||||
if type(listener) ~= "table" then
|
||||
return
|
||||
end
|
||||
table.insert(self._completeListeners, listener)
|
||||
end
|
||||
|
||||
function Mission:NotifyMissionComplete()
|
||||
self._missionCommandsHelper:RemoveMissionToCommands(self)
|
||||
self._logger:info("Mission Completed: " .. self.zoneName)
|
||||
trigger.action.outText("Mission " .. self.name .. " [" .. self.code .. "] was completed succesfully", 20)
|
||||
|
||||
for _, listener in pairs(self._completeListeners) do
|
||||
pcall(function()
|
||||
listener:OnMissionComplete(self)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
---endregion
|
||||
|
||||
--region PROTECTED
|
||||
|
||||
---@protected
|
||||
function Mission:ToStateString() return "status: in progress" end
|
||||
|
||||
|
||||
--endregion
|
||||
|
||||
|
||||
|
||||
if not Spearhead.classes then Spearhead.classes = {} end
|
||||
if not Spearhead.classes.stageClasses then Spearhead.classes.stageClasses = {} end
|
||||
if not Spearhead.classes.stageClasses.missions then Spearhead.classes.stageClasses.missions = {} end
|
||||
if not Spearhead.classes.stageClasses.missions.baseMissions then Spearhead.classes.stageClasses.missions.baseMissions = {} end
|
||||
Spearhead.classes.stageClasses.missions.baseMissions.Mission = Mission
|
||||
|
||||
|
||||
|
||||
|
||||
do --aliases
|
||||
|
||||
--- @alias MissionPriority
|
||||
--- | "none"
|
||||
--- | "primary"
|
||||
--- | "secondary"
|
||||
|
||||
--- @alias MissionType
|
||||
--- | "nil"
|
||||
--- | "STRIKE"
|
||||
--- | "BAI"
|
||||
--- | "DEAD"
|
||||
--- | "SAM"
|
||||
--- | "OCA"
|
||||
|
||||
--- @alias MissionState
|
||||
--- | "NEW"
|
||||
--- | "ACTIVE"
|
||||
--- | "COMPLETED"
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ SpearheadConfig = {
|
||||
|
||||
--Will draw the active and the next stage
|
||||
drawStages = true, -- default true
|
||||
drawPreActivated = true, -- default true
|
||||
|
||||
--AutoStages will continue to the next stage automatically on completion of the missions within the stage.
|
||||
-- If you want to make it so the next stage triggers only when you want to disable it here and manually implement the actions needed.
|
||||
@@ -54,6 +55,10 @@ SpearheadConfig = {
|
||||
--Stage starting number
|
||||
startingStage = 1,
|
||||
|
||||
-- Amount of stages that are pre-activated on top of the current active stage.
|
||||
-- In Pre-activated the missions are not listed, but SAMs and Airbase groups are spawned.
|
||||
preactivateStage = 1, -- default 1
|
||||
|
||||
---DEBUG logging. Consider keeping this disabled
|
||||
debugEnabled = false
|
||||
},
|
||||
|
||||
+7
-1
@@ -6,6 +6,8 @@
|
||||
local basePath = "C:\\Repos\\DCS\\Spearhead\\"
|
||||
local classPath = basePath .. "classes\\"
|
||||
|
||||
assert(loadfile(classPath .. "_baseClasses\\Queue.lua"))()
|
||||
|
||||
assert(loadfile(classPath .. "spearhead_base.lua"))()
|
||||
assert(loadfile(classPath .. "spearhead_routeutil.lua"))()
|
||||
assert(loadfile(classPath .. "spearhead_events.lua"))()
|
||||
@@ -18,7 +20,10 @@ assert(loadfile(classPath .. "configuration\\CapConfig.lua"))()
|
||||
assert(loadfile(classPath .. "configuration\\StageConfig.lua"))()
|
||||
|
||||
assert(loadfile(classPath .. "stageClasses\\GlobalStageManager.lua"))()
|
||||
assert(loadfile(classPath .. "stageClasses\\Missions\\Mission.lua"))()
|
||||
assert(loadfile(classPath .. "stageClasses\\missions\\baseMissions\\Mission.lua"))()
|
||||
assert(loadfile(classPath .. "stageClasses\\missions\\ZoneMission.lua"))()
|
||||
assert(loadfile(classPath .. "stageClasses\\missions\\RunwayStrikeMission.lua"))()
|
||||
|
||||
|
||||
assert(loadfile(classPath .. "stageClasses\\Stages\\BaseStage\\Stage.lua"))()
|
||||
assert(loadfile(classPath .. "stageClasses\\Stages\\PrimaryStage.lua"))()
|
||||
@@ -36,6 +41,7 @@ assert(loadfile(classPath .. "stageClasses\\SpecialZones\\BlueSam.lua"))()
|
||||
assert(loadfile(classPath .. "capClasses\\CapGroup.lua"))()
|
||||
assert(loadfile(classPath .. "capClasses\\GlobalCapManager.lua"))()
|
||||
assert(loadfile(classPath .. "capClasses\\CapAirbase.lua"))()
|
||||
assert(loadfile(classPath .. "capClasses\\runwayBombing\\RunwayBombingTracker.lua"))()
|
||||
|
||||
assert(loadfile(classPath .. "persistence\\Persistence.lua"))()
|
||||
|
||||
|
||||
+2
-2
@@ -52,7 +52,7 @@ SpearheadConfig = {
|
||||
maxMissionStage = 10,
|
||||
|
||||
--Stage starting number
|
||||
startingStage = 1,
|
||||
startingStage = 0,
|
||||
|
||||
---DEBUG logging. Consider keeping this disabled
|
||||
debugEnabled = true
|
||||
@@ -61,7 +61,7 @@ SpearheadConfig = {
|
||||
--- io and lfs cannot be sanitized in the MissionScripting.lua
|
||||
|
||||
--- enables or disables the persistence logic in spearhead
|
||||
enabled = true,
|
||||
enabled = false,
|
||||
|
||||
--- sets the directory where the persistence file is stored
|
||||
--- if nil then lfs.writedir() will be used.
|
||||
|
||||
@@ -36,8 +36,8 @@ else
|
||||
standardLogger:info("Persistence disabled")
|
||||
end
|
||||
|
||||
Spearhead.internal.GlobalCapManager.start(databaseManager, capConfig, stageConfig)
|
||||
Spearhead.internal.GlobalStageManager:NewAndStart(databaseManager, stageConfig)
|
||||
Spearhead.classes.capClasses.GlobalCapManager.start(databaseManager, capConfig, stageConfig, defaultLogLevel)
|
||||
Spearhead.internal.GlobalStageManager:NewAndStart(databaseManager, stageConfig, defaultLogLevel)
|
||||
Spearhead.internal.GlobalFleetManager.start(databaseManager)
|
||||
|
||||
local SetStageDelayed = function(number, time)
|
||||
|
||||
Reference in New Issue
Block a user