linter fixes
PR Prerequisites / Lua Check (pull_request) Failing after 20s
PR Prerequisites / Bundles Without Errors (pull_request) Failing after 28s

This commit is contained in:
2026-09-09 11:12:58 +02:00
parent ba240db310
commit 0346f8a7a8
40 changed files with 367 additions and 536 deletions
+3 -3
View File
@@ -15,7 +15,7 @@ SpearheadConfig = {
--quickly enable of disable the entire CAP Logic
--(you can also just rename all units to not be named "CAP_")
enabled = true, -- default true
--min ground speed for CAP aircraft during patrol
-- unit: knots
minSpeed = 400, -- default 400
@@ -62,7 +62,7 @@ SpearheadConfig = {
deathDelay = 1800, -- default 1800
},
StageConfig = {
-- management of stages and its missions.
-- This is not related to CAP managers which will continue to work even if stage management is disabled
enabled = true, -- default true
@@ -98,7 +98,7 @@ SpearheadConfig = {
},
Persistence = {
--- io and lfs cannot be sanitized in the MissionScripting.lua
--- enables or disables the persistence logic in spearhead
enabled = false,
-73
View File
@@ -1,73 +0,0 @@
--assert(loadfile("C:\\Repos\\DCS\\Spearhead\\dev\\dev_classes.lua"))()
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"))()
assert(loadfile(classPath .. "spearhead_db.lua"))()
assert(loadfile(classPath .. "fleetClasses\\FleetGroup.lua"))()
assert(loadfile(classPath .. "fleetClasses\\GlobalFleetManager.lua"))()
assert(loadfile(classPath .. "helpers\\MizGroupsManager.lua"))()
assert(loadfile(classPath .. "helpers\\SpawnManager.lua"))()
assert(loadfile(classPath .. "configuration\\CapConfig.lua"))()
assert(loadfile(classPath .. "configuration\\StageConfig.lua"))()
assert(loadfile(classPath .. "stageClasses\\GlobalStageManager.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\\missions\\BuildableMission.lua"))()
assert(loadfile(classPath .. "stageClasses\\Stages\\BaseStage\\Stage.lua"))()
assert(loadfile(classPath .. "stageClasses\\Stages\\PrimaryStage.lua"))()
assert(loadfile(classPath .. "stageClasses\\Stages\\ExtraStage.lua"))()
assert(loadfile(classPath .. "stageClasses\\Stages\\WaitingStage.lua"))()
assert(loadfile(classPath .. "stageClasses\\Groups\\SpearheadGroup.lua"))()
assert(loadfile(classPath .. "stageClasses\\Groups\\SpearheadSceneryObject.lua"))()
assert(loadfile(classPath .. "stageClasses\\helpers\\MissionCommandsHelper.lua"))()
assert(loadfile(classPath .. "stageClasses\\helpers\\SupplyConfig.lua"))()
assert(loadfile(classPath .. "stageClasses\\helpers\\SupplyUnitsTracker.lua"))()
assert(loadfile(classPath .. "stageClasses\\helpers\\BattleManager.lua"))()
assert(loadfile(classPath .. "stageClasses\\SpecialZones\\abstract\\BuildableZone.lua"))()
assert(loadfile(classPath .. "stageClasses\\SpecialZones\\StageBase.lua"))()
assert(loadfile(classPath .. "stageClasses\\SpecialZones\\BlueSam.lua"))()
assert(loadfile(classPath .. "stageClasses\\SpecialZones\\FarpZone.lua"))()
assert(loadfile(classPath .. "stageClasses\\SpecialZones\\SupplyHub.lua"))()
assert(loadfile(classPath .. "capClasses\\taskings\\RTB.lua"))()
assert(loadfile(classPath .. "capClasses\\taskings\\CAP.lua"))()
assert(loadfile(classPath .. "capClasses\\taskings\\SWEEP.lua"))()
assert(loadfile(classPath .. "capClasses\\taskings\\INTERCEPT.lua"))()
assert(loadfile(classPath .. "capClasses\\airGroups\\AirGroup.lua"))()
assert(loadfile(classPath .. "capClasses\\airGroups\\CapGroup.lua"))()
assert(loadfile(classPath .. "capClasses\\airGroups\\SweepGroup.lua"))()
assert(loadfile(classPath .. "capClasses\\airGroups\\InterceptGroup.lua"))()
assert(loadfile(classPath .. "capClasses\\detection\\DetectionManager.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"))()
-- Startup:
assert(loadfile(basePath .. "main.lua"))()
-79
View File
@@ -1,79 +0,0 @@
SpearheadConfig = {
debugEnabled = true,
CapConfig = {
--quickly enable of disable the entire CAP Logic
--(you can also just rename all units to not be named "CAP_")
enabled = true, -- default true
--min ground speed for CAP aircraft during patrol
-- unit: knots
minSpeed = 400, -- default 400
--max speed for CAP aircraft during patrol
-- unit: knots
maxSpeed = 500, -- default 500
--minAlt for aircraft on patrol
-- unit: feet
minAlt = 18000, -- default 18000
--maxAlt for aircraft on patrol
-- unit: feet
maxAlt = 28000, -- default 28000
--Delay for aircraft from touchdown to off the chocks.
-- unit: seconds
rearmDelay = 180, -- default 600
repairDelay = 600, -- default 600
--Delay for aircraft from death to takeoff.
--When the seconds remaining is the same at the rearmDelay it will be spawned on the ramp and follow the rearm logic.
-- !! Can not be lower than rearmDelay
-- unit: seconds
deathDelay = 1800, -- default 1800
},
StageConfig = {
-- management of stages and its missions.
-- This is not related to CAP managers which will continue to work even if stage management is disabled
enabled = true, -- default true
--Will draw the active and the next stage
drawStages = true, -- default true
drawPreActivated = true,
markLastContact = 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.
--[[
TODO: Add manual stage transition documentation
]]
autoStages = true, --default true
--Maximum missions per stage (includes all types of missions)
maxMissionStage = 100,
--Stage starting number
startingStage = 1,
---DEBUG logging. Consider keeping this disabled
debugEnabled = true
},
Persistence = {
--- io and lfs cannot be sanitized in the MissionScripting.lua
--- enables or disables the persistence logic in spearhead
enabled = true,
--- sets the directory where the persistence file is stored
--- if nil then lfs.writedir() will be used.
--- which will
directory = nil ,
--- the filename of the persistence file. Should end with .json for convention, but any text extension should do.
fileName = "Spearhead_Persistence_Dev"
}
}
+8 -7
View File
@@ -95,7 +95,6 @@ function CapBase.new(airbaseName, database, logger, capConfig, runwayBombingTrac
SpearheadEvents.AddStageNumberChangedListener(self)
timer.scheduleFunction(CheckStateContinuous, self, timer.getTime() + 15)
return self
end
@@ -152,8 +151,9 @@ end
function CapBase:CheckAndScheduleCAP()
self.logger:debug("Check taskings for airbase " .. self.airbaseName)
---@type table<string, number>
local countPerStage = {}
---@type table<string, number>
local requiredPerStage = {}
local airbase = Airbase.getByName(self.airbaseName)
@@ -168,7 +168,7 @@ function CapBase:CheckAndScheduleCAP()
if group:IsBackup() == true then
local state = group:GetState()
if state == "InTransit" or state == "OnStation" or state == "RtbInTen" then
local supposedTargetZoneID = group:GetZoneIDWhenStageID(activeStageID)
local currentTargetZone = group:GetCurrentTargetZoneID()
@@ -190,7 +190,7 @@ function CapBase:CheckAndScheduleCAP()
end
end
end
if countPerStage[supposedTargetZoneID] == nil then
countPerStage[supposedTargetZoneID] = 0
end
@@ -229,7 +229,7 @@ function CapBase:CheckAndScheduleCAP()
countPerStage[supposedZone] = countPerStage[supposedZone] + 1
end
elseif state == "InTransit" or state == "OnStation" then
if supposedZone ~= group:GetCurrentTargetZoneID() then
if countPerStage[supposedZone] < requiredPerStage[supposedZone] then
local triggerZone = self.database:GetCapZoneForZoneID(supposedZone)
@@ -271,7 +271,7 @@ function CapBase:CheckAndScheduleCAP()
if triggerZone then
group:SendToZone(triggerZone, supposedZone, airbase)
end
countPerStage[supposedZone] = countPerStage[supposedZone] + 1
end
end
@@ -310,6 +310,7 @@ function CapBase:CheckAndScheduleIntercept()
self.logger:debug("Check intercept taskings for airbase " .. self.airbaseName)
---@type table<string, boolean>
local interceptZoneIDs = {}
local airbase = Airbase.getByName(self.airbaseName)
@@ -353,7 +354,7 @@ function CapBase:CheckAndScheduleIntercept()
end
end
end
---RATIO. Amount of enemy fighters required per zone before another group gets added.
local ratio = 4 -- Ratio of units to intercept per zone, can be adjusted
+1 -2
View File
@@ -35,9 +35,8 @@ do
for _, airbaseName in pairs(airbaseNames) do
if airbaseName then
local airbaseSpecificLogger = Logger.new("CAP_" .. airbaseName, logLevel)
local airbase = CapAirbase.new(airbaseName, database, airbaseSpecificLogger, capConfig, runwayBombingTracker, detectionManager, spawnManager)
if airbase then
table.insert(airbasesPerStage[stageName], airbase)
allAirbasesByName[airbaseName] = airbase
@@ -103,7 +103,7 @@ function CapGroup:InitWithName(groupName)
return
end
local subsplit = Util.split_string(configPart, "|")
if subsplit then
@@ -112,8 +112,8 @@ function CapGroup:InitWithName(groupName)
local targetZone = keySplit[2]
local allActives = string.sub(keySplit[1], 2, #keySplit[1])
local commaSeperated = Util.split_string(allActives, ",")
for _, value in pairs(commaSeperated) do
local dashSeperated = Util.split_string(value, "-")
for _, childValue in pairs(commaSeperated) do
local dashSeperated = Util.split_string(childValue, "-")
if Util.tableLength(dashSeperated) > 1 then
local from = tonumber(dashSeperated[1])
local till = tonumber(dashSeperated[2])
@@ -190,7 +190,7 @@ function InterceptGroup:UpdateTask()
self:SendRTB(self._airbase)
return nil -- Return to base if bingo fuel
end
if selfDetected and self:IsInAir() == true then
if self._currentTargetName and self._currentTargetName == closestUnit:getName() then
local distance = Util.VectorDistance3d(closestUnit:getPoint(), groupPoint)
@@ -213,8 +213,6 @@ function InterceptGroup:UpdateTask()
self._currentTargetName = nil
end
local speed = self._config:getMaxSpeed()
local interceptPoint = self:GetInterceptPoint(groupPoint, speed, closestUnit)
@@ -328,8 +326,8 @@ function InterceptGroup:InitWithName(groupName)
local targetZone = keySplit[2]
local allActives = string.sub(keySplit[1], 2, #keySplit[1])
local commaSeperated = Util.split_string(allActives, ",")
for _, value in pairs(commaSeperated) do
local dashSeperated = Util.split_string(value, "-")
for _, childValue in pairs(commaSeperated) do
local dashSeperated = Util.split_string(childValue, "-")
if Util.tableLength(dashSeperated) > 1 then
local from = tonumber(dashSeperated[1])
local till = tonumber(dashSeperated[2])
@@ -77,7 +77,7 @@ function SweepGroup:InitWithName(groupName)
local configPart = split_string[2]
configPart = string.sub(configPart, 2, #configPart)
local subsplit = Util.split_string(configPart, "|")
if subsplit then
for _, value in pairs(subsplit) do
@@ -85,8 +85,8 @@ function SweepGroup:InitWithName(groupName)
local targetZone = keySplit[2]
local allActives = string.sub(keySplit[1], 2, #keySplit[1])
local commaSeperated = Util.split_string(allActives, ",")
for _, value in pairs(commaSeperated) do
local dashSeperated = Util.split_string(value, "-")
for _, childValue in pairs(commaSeperated) do
local dashSeperated = Util.split_string(childValue, "-")
if Util.tableLength(dashSeperated) > 1 then
local from = tonumber(dashSeperated[1])
local till = tonumber(dashSeperated[2])
@@ -37,7 +37,6 @@ function DetectionManager.New(logger)
return time + 10
end
timer.scheduleFunction(updateDetected, self, timer.getTime() + 130)
return self
end
@@ -30,7 +30,7 @@ function RunwayBombingTracker:OnWeaponFired(_, weapon, _)
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,
@@ -61,7 +61,7 @@ function RunwayBombingTracker.trackWeaponTask(weaponTrackingArgs, time)
local weapon = weaponTrackingArgs.weapon
local self = weaponTrackingArgs.self
if not weapon or weapon:isExist() == false then return nil end
local pos = weapon:getPoint()
@@ -83,11 +83,7 @@ function RunwayBombingTracker.trackWeaponTask(weaponTrackingArgs, time)
x = pos.x + velocity.x * nextInterval,
y = pos.z + velocity.z * nextInterval
}
self:OnWeaponImpact(weapon:getDesc(), impactPoint)
return nil
end
@@ -38,7 +38,7 @@ function INTERCEPT.getMissionFromAirbase(groupName, interceptPoint, airbase, con
id = 'Mission',
params = {
airborne = true,
route = {
route = {
points = points
}
}
@@ -159,10 +159,10 @@ function INTERCEPT.getInterceptTaskPoint(groupName, currentPoint, targetPoint, a
params = {
tasks = {
id = 'EngageTargetsInZone',
params = {
params = {
point = targetPoint,
zoneRadius = 10 * 1852, -- 10 NM, point will be updated, so target should be in this zone.
targetTypes = {
targetTypes = {
[1] = "Planes",
},
priority = 0
@@ -188,10 +188,10 @@ function INTERCEPT.getInterceptTaskPoint(groupName, currentPoint, targetPoint, a
params = {
tasks = {
id = 'EngageTargetsInZone',
params = {
params = {
point = targetPoint,
zoneRadius = 10 * 1852, -- 10 NM, point will be updated, so target should be in this zone.
targetTypes = {
targetTypes = {
[1] = "Planes",
},
priority = 0
+3 -3
View File
@@ -47,7 +47,7 @@ local getRunwayIntoWindCourseRad = function(airbase)
else
rad = 0 - rad
end
local runwayVec = {x = math.cos(rad), z = math.sin(rad), y = 0}
local alignment = Util.vectorAlignment(windVec, runwayVec)
@@ -60,14 +60,14 @@ local getRunwayIntoWindCourseRad = function(airbase)
do --inverse
local degree = math.deg(runway.course)
degree = (degree + 180) % 360
local rad = math.rad(degree)
if rad < 0 then
rad = math.abs(rad)
else
rad = 0 - rad
end
local runwayVec = {x = math.cos(rad), z = math.sin(rad), y = 0}
local alignment = Util.vectorAlignment(windVec, runwayVec)
+1 -1
View File
@@ -18,7 +18,7 @@ local _logger = Logger.new("StageConfig", Logger.LogLevel)
---@return StageConfig
local function new()
if SpearheadConfig == nil then
if SpearheadConfig == nil then
_logger:warn("SpearheadConfig is nil, creating default SpearheadConfig")
SpearheadConfig = {}
end
+48 -42
View File
@@ -4,37 +4,43 @@ local MissionEditorWarning = require("classes.util.MissionEditorWarnings")
local RouteUtil = require("classes.spearhead_routeutil")
local SpearheadEvents = require("classes.spearhead_events")
---@class FleetGroup
---@class FleetGroup : OnStageChangedListener
---@field private fleetGroupName string
---@field private logger Logger
---@field private fleetNameIdentifier string
---@field private targetZonePerStage table<string, string>
---@field private currentTargetZone string
---@field private pointsPerZone table<string, {pointA: Vec2, pointB: Vec2}>
local FleetGroup = {}
FleetGroup.__index = FleetGroup
---comment
---@param fleetGroupName string
---@param database Database
---@param logger Logger
---@return nil
function FleetGroup:new(fleetGroupName, database, logger)
local o = {}
---@return FleetGroup?
function FleetGroup.new(fleetGroupName, database, logger)
setmetatable(o, { __index = self })
local self = setmetatable({}, FleetGroup)
o.fleetGroupName = fleetGroupName
o.logger = logger
self.fleetGroupName = fleetGroupName
self.logger = logger
local split_name = Util.split_string(fleetGroupName, "_")
if Util.tableLength(split_name) < 2 then
MissionEditorWarning.Add("CARRIERGROUP should have at least 2 parts. CARRIERGROUP_<fleetname>")
return nil
end
o.fleetNameIdentifier = split_name[2]
self.fleetNameIdentifier = split_name[2]
o.targetZonePerStage = {}
o.currentTargetZone = nil
o.pointsPerZone = {}
self.targetZonePerStage = {}
self.currentTargetZone = nil
self.pointsPerZone = {}
do --INIT
local carrierRouteZones = database:getCarrierRouteZones()
for _, zoneName in pairs(carrierRouteZones) do
if Util.strContains(string.lower(zoneName), "_".. string.lower(o.fleetNameIdentifier) .. "_" ) == true then
if Util.strContains(string.lower(zoneName), "_".. string.lower(self.fleetNameIdentifier) .. "_" ) == true then
local zone = DcsUtil.getZoneByName(zoneName)
if zone and zone.zone_type == DcsUtil.ZoneType.Polygon then
local split_string = Util.split_string(zoneName, "_")
@@ -43,18 +49,18 @@ function FleetGroup:new(fleetGroupName, database, logger)
"CARRIERROUTE should at least have 3 parts. Check the documentation for: " .. zoneName)
else
---@param zone SpearheadTriggerZone
---@param zoneA SpearheadTriggerZone
---@return Vec2, Vec2
local function GetTwoFurthestPoints(zone)
local function GetTwoFurthestPoints(zoneA)
local biggest = nil
local biggestA = zone.verts[1]
local biggestB = zone.verts[2]
local biggestA = zoneA.verts[1]
local biggestB = zoneA.verts[2]
for i = 1, 3 do
for ii = i + 1, 4 do
local a = zone.verts[i]
local b = zone.verts[ii]
local a = zoneA.verts[i]
local b = zoneA.verts[ii]
local dist = Util.VectorDistance2d(a, b)
if biggest == nil or dist > biggest then
@@ -100,9 +106,9 @@ function FleetGroup:new(fleetGroupName, database, logger)
local first, second = getMinMaxStage(split_string[3])
if first ~= nil and second ~= nil then
for i = first, second do
o.targetZonePerStage[tostring(i)] = zoneName
self.targetZonePerStage[tostring(i)] = zoneName
end
o.pointsPerZone[zoneName] = { pointA = { x = pointA.x, z = pointA.y, y = 0 }, pointB = { x = pointB.x, z = pointB.y, y = 0} }
self.pointsPerZone[zoneName] = { pointA = { x = pointA.x, z = pointA.y, y = 0 }, pointB = { x = pointB.x, z = pointB.y, y = 0} }
else
MissionEditorWarning.Add("CARRIERROUTE zone stage numbers not in the format _[<number>-<number>]: " .. zoneName)
end
@@ -114,32 +120,32 @@ function FleetGroup:new(fleetGroupName, database, logger)
end
end
local SetTaskAsync = function(input, _)
local targetZone = input.targetZone
local task = input.task
local groupName = input.groupName
local logger = input.logger
SpearheadEvents.AddStageNumberChangedListener(self)
return self
end
local group = Group.getByName(groupName)
if group then
logger:info("Sending " .. groupName .. " to " .. targetZone)
group:getController():setTask(task)
end
local SetTaskAsync = function(input, _)
local targetZone = input.targetZone
local task = input.task
local groupName = input.groupName
local l_logger = input.logger
local group = Group.getByName(groupName)
if group then
l_logger:info("Sending " .. groupName .. " to " .. targetZone)
group:getController():setTask(task)
end
end
o.OnStageNumberChanged = function(self, number, laneIdentifier)
-- only react on "main" lane changes, ignore other lanes for now
if laneIdentifier ~= nil then return end
local targetZone = self.targetZonePerStage[tostring(number)]
if targetZone and targetZone ~= self.currentTargetZone then
local points = self.pointsPerZone[targetZone]
local task = RouteUtil.CreateCarrierRacetrack(points.pointA, points.pointB)
timer.scheduleFunction(SetTaskAsync, { task = task, targetZone = targetZone, groupName = self.fleetGroupName, logger = self.logger }, timer.getTime() + 5)
end
function FleetGroup:OnStageNumberChanged(number, laneIdentifier)
-- only react on "main" lane changes, ignore other lanes for now
if laneIdentifier ~= nil then return end
local targetZone = self.targetZonePerStage[tostring(number)]
if targetZone and targetZone ~= self.currentTargetZone then
local points = self.pointsPerZone[targetZone]
local task = RouteUtil.CreateCarrierRacetrack(points.pointA, points.pointB)
timer.scheduleFunction(SetTaskAsync, { task = task, targetZone = targetZone, groupName = self.fleetGroupName, logger = self.logger }, timer.getTime() + 5)
end
SpearheadEvents.AddStageNumberChangedListener(o)
return o
end
return FleetGroup
@@ -17,7 +17,7 @@ GlobalFleetManager.start = function(database)
for _, groupName in pairs(all_groups) do
if Util.startswith(string.lower(groupName), "carriergroup" ) == true then
logger:info("Registering " .. groupName .. " as a managed fleet")
local carrierGroup = FleetGroup:new(groupName, database, logger)
local carrierGroup = FleetGroup.new(groupName, database, logger)
table.insert(fleetGroups, carrierGroup)
end
end
+2 -3
View File
@@ -33,7 +33,7 @@ end
---@return StaticObject|Group|nil spawned
---@return boolean isStatic
function SpawnManager:SpawnGroup(groupName, overrides, isGroupPersistant)
local spawnData = MizGroupsManager.getSpawnTemplateData(groupName)
if spawnData == nil then
@@ -50,7 +50,7 @@ end
function SpawnManager:DestroyGroup(groupName)
if groupName == nil then return end
if self:IsGroupStatic(groupName) == true then
local object = StaticObject.getByName(groupName)
if object ~= nil then
@@ -181,7 +181,6 @@ do --- privates
return group
end
return nil
end
+5 -5
View File
@@ -89,7 +89,7 @@ do
else
logger:error("Could not load persistence file, using default tables")
end
if tables.version == nil then tables.version = version end
end
@@ -114,7 +114,7 @@ do
local UpdateContinuous = function(_, time)
env.info("[Spearhead][Persistence] Checking up on persistence state...")
if Persistence._updateRequired == true then
if Persistence._updateRequired == true then
local status, result = pcall(writeToFile)
if status == false then
env.error("[Spearhead][Persistence] Could not write state to file: " .. result)
@@ -194,7 +194,7 @@ do
split[#split+1] = "0"
split[#split+1] = "spearhead"
end
fileName = table.concat(split, ".")
end
@@ -218,12 +218,12 @@ do
logger:info("No previous persistence file found, using default: " .. fileName)
end
logger:info("New Persistence file name: " .. tostring(fileName))
local lastPath = dir .. "\\" .. lastFile
local path = dir .. "\\" .. fileName
Persistence._path = path
createFileIfNotExists(path)
loadTablesFromFile(lastPath)
timer.scheduleFunction(UpdateContinuous, nil, timer.getTime() + 120)
+12 -14
View File
@@ -258,7 +258,7 @@ function Database.New(Logger)
if env.mission.drawings and env.mission.drawings.layers then
for _, layer in pairs(env.mission.drawings.layers) do
if string.lower(layer.name) == "author" then
for key, layer_object in pairs(layer.objects) do
for _, layer_object in pairs(layer.objects) do
if Util.startswith(string.lower(layer_object.name), "stagebriefing_", true) == true then
local zone = DcsUtil.getZoneByName(stageZoneName)
local vec2 = { x = layer_object.mapX, y = layer_object.mapY }
@@ -339,14 +339,14 @@ function Database.New(Logger)
end
-- fill missions
for key, missionZone in pairs(self._tables.MissionZones) do
for _, missionZone in pairs(self._tables.MissionZones) do
if DcsUtil.isZoneInZone(missionZone, stageZoneName) == true then
table.insert(stageData.MissionZones, missionZone)
end
end
-- fill random missions
for key, missionZone in pairs(self._tables.RandomMissionZones) do
for _, missionZone in pairs(self._tables.RandomMissionZones) do
if DcsUtil.isZoneInZone(missionZone, stageZoneName) == true then
table.insert(stageData.RandomMissionZones, missionZone)
end
@@ -620,9 +620,9 @@ function Database:loadBlueSamUnits()
samData.buildingKilos = number
if env.mission.drawings and env.mission.drawings.layers then
for i, layer in pairs(env.mission.drawings.layers) do
for _, layer in pairs(env.mission.drawings.layers) do
if string.lower(layer.name) == "author" then
for key, layer_object in pairs(layer.objects) do
for _, layer_object in pairs(layer.objects) do
local vec2 = { x = layer_object.mapX, y = layer_object.mapY }
if triggerZone and Util.is2dPointInZone(vec2, triggerZone) then
@@ -637,7 +637,7 @@ function Database:loadBlueSamUnits()
end
end
end
else
else
MissionEditorWarnings.Add("Buildable number for " .. blueSamZone .. " is invalid")
end
end
@@ -670,13 +670,13 @@ function Database:LoadZoneData(missionZoneName)
for _, groupName in pairs(groups) do
if MizGroupsManager.IsGroupStatic(groupName) == true then
local object = StaticObject.getByName(groupName)
if object and object:getCoalition() == coalition.side.RED then
table.insert(self._tables.MissionZoneData[missionZoneName].RedGroups, groupName)
elseif object then
table.insert(self._tables.MissionZoneData[missionZoneName].BlueGroups, groupName)
end
else
local group = Group.getByName(groupName)
if group and group:getCoalition() == coalition.side.RED then
@@ -720,9 +720,9 @@ function Database:LoadZoneData(missionZoneName)
---Adds Briefings
if env.mission.drawings and env.mission.drawings.layers then
for i, layer in pairs(env.mission.drawings.layers) do
for _, layer in pairs(env.mission.drawings.layers) do
if string.lower(layer.name) == "author" then
for key, layer_object in pairs(layer.objects) do
for _, layer_object in pairs(layer.objects) do
local vec2 = { x = layer_object.mapX, y = layer_object.mapY }
if triggerZone and Util.is2dPointInZone(vec2, triggerZone) then
@@ -776,9 +776,9 @@ function Database:loadFarpData()
-- check briefings
if env.mission.drawings and env.mission.drawings.layers then
for i, layer in pairs(env.mission.drawings.layers) do
for _, layer in pairs(env.mission.drawings.layers) do
if string.lower(layer.name) == "author" then
for key, layer_object in pairs(layer.objects) do
for _, layer_object in pairs(layer.objects) do
local vec2 = { x = layer_object.mapX, y = layer_object.mapY }
if triggerZone and Util.is2dPointInZone(vec2, triggerZone) then
@@ -796,8 +796,6 @@ function Database:loadFarpData()
else
MissionEditorWarnings.Add("Buildable number for " .. farpZone .. " is invalid.")
end
end
end
end
+27 -28
View File
@@ -46,7 +46,7 @@ do
---@class OnStageNumberChangeCompleteListener
---@field OnStageNumberChangeComplete fun(self:OnStageNumberChangeCompleteListener, number:integer, laneIdentifier:string?)
local OnStageNumberChangeCompleteListeners = {}
---@param listener OnStageNumberChangeCompleteListener
SpearheadEvents.AddStageNumberChangeCompleteListener = function(listener)
@@ -65,7 +65,7 @@ do
end)
for _, callable in pairs(OnStageNumberChangedListeners) do
local succ, err = pcall(function()
local _, err = pcall(function()
callable:OnStageNumberChanged(newStageNumber, laneIdentifier)
end)
if err then
@@ -74,14 +74,14 @@ do
end
for _, callable in pairs(OnStageNumberChangedHandlers) do
local succ, err = pcall(callable, newStageNumber, laneIdentifier)
local _, err = pcall(callable, newStageNumber, laneIdentifier)
if err then
logError(err)
end
end
for _, callable in pairs(OnStageNumberChangeCompleteListeners) do
local succ, err = pcall(function()
local _, err = pcall(function()
callable:OnStageNumberChangeComplete(newStageNumber, laneIdentifier)
end)
if err then
@@ -112,7 +112,7 @@ do
local triggerWeaponFired = function(unit, weapon, target)
for _, callable in pairs(onWeaponFiredListeners) do
local succ, err = pcall(function()
local _, err = pcall(function()
callable:OnWeaponFired(unit, weapon, target)
end)
@@ -121,7 +121,7 @@ do
end
end
end
local onLandEventListeners = {}
---Add an event listener to a specific unit
@@ -186,7 +186,7 @@ do
if groupName ~= nil then
if OnGroupRTBListeners[groupName] then
for _, callable in pairs(OnGroupRTBListeners[groupName]) do
local succ, err = pcall(function()
local _, err = pcall(function()
callable:OnGroupRTB(groupName)
end)
if err then
@@ -221,7 +221,7 @@ do
if groupName ~= nil then
if OnGroupRTBInTenListeners[groupName] then
for _, callable in pairs(OnGroupRTBInTenListeners[groupName]) do
local succ, err = pcall(function()
local _, err = pcall(function()
callable:OnGroupRTBInTen(groupName)
end)
if err then
@@ -257,7 +257,7 @@ do
if groupName ~= nil then
if OnGroupOnStationListeners[groupName] then
for _, callable in pairs(OnGroupOnStationListeners[groupName]) do
local succ, err = pcall(function()
local _, err = pcall(function()
callable:OnGroupOnStation(groupName)
end)
if err then
@@ -286,7 +286,7 @@ do
if unit ~= nil then
if playerEnterUnitListeners then
for _, callable in pairs(playerEnterUnitListeners) do
local succ, err = pcall(function()
local _, err = pcall(function()
callable:OnPlayerEntersUnit(unit)
end)
if err then
@@ -299,7 +299,7 @@ do
end
do -- Ejection events
local unitEjectListeners = {}
SpearheadEvents.AddOnUnitEjectedListener = function(listener)
if type(listener) ~= "table" then
@@ -321,7 +321,7 @@ do
local name = unit:getName()
if onLandEventListeners[name] then
for _, callable in pairs(onLandEventListeners[name]) do
local succ, err = pcall(function()
local _, err = pcall(function()
callable:OnUnitLanded(unit, airbase)
end)
if err then
@@ -341,10 +341,10 @@ do
if object and object.getName then
logDebug("Receiving death event from: " .. object:getName())
end
if object and object.getName and OnUnitLostListeners[object:getName()] then
for _, callable in pairs(OnUnitLostListeners[object:getName()]) do
local succ, err = pcall(function()
local _, err = pcall(function()
callable:OnUnitLost(object)
end)
@@ -356,11 +356,11 @@ do
end
if event.id == world.event.S_EVENT_EJECTION then
end
if event.id == world.event.S_EVENT_SHOT then
local shooter = event.initiator
local weapon = event.weapon
local target = event.target
@@ -374,26 +374,26 @@ do
local AI_GROUPS = {}
local function CheckAndTriggerSpawnAsync(unit, time)
local function isPlayer(unit)
if unit == nil then return false, "unit is nil" end
if unit.getGroup == nil then return false, 'no get group function in unit object, most likely static' end
if Object.getCategory(unit) ~= Object.Category.UNIT then
local function CheckAndTriggerSpawnAsync(unit, _)
local function isPlayer(checkUnit)
if checkUnit == nil then return false, "unit is nil" end
if checkUnit.getGroup == nil then return false, 'no get group function in unit object, most likely static' end
if Object.getCategory(checkUnit) ~= Object.Category.UNIT then
return false, "object is not a unit"
end
if unit:isExist() ~= true then return false, "unit does not exist" end
local group = unit:getGroup()
if checkUnit:isExist() ~= true then return false, "unit does not exist" end
local group = checkUnit:getGroup()
if group ~= nil then
if AI_GROUPS[group:getName()] == true then
return false
end
local players = DcsUtil.getAllPlayerUnits()
local unitName = unit:getName()
for i, unit in pairs(players) do
if unit:getName() == unitName then
local unitName = checkUnit:getName()
for _, playerUnit in pairs(players) do
if playerUnit:getName() == unitName then
return true
end
end
@@ -403,7 +403,6 @@ do
end
if isPlayer(unit) == true then
local groupId = unit:getGroup():getID()
SpearheadEvents.TriggerPlayerEntersUnit(unit)
end
+105 -97
View File
@@ -2,7 +2,6 @@ local Events = require("classes.spearhead_events")
local Util = require("classes.util.Util")
local DcsUtil = require("classes.util.DcsUtil")
local Logger = require("classes.util.Logger")
local MissionEditorWarnings = require("classes.util.MissionEditorWarnings")
local PersistenceConfig = require("classes.configuration.PersistenceConfig")
local ExtraStage = require("classes.stageClasses.Stages.ExtraStage")
local PrimaryStage = require("classes.stageClasses.Stages.PrimaryStage")
@@ -35,7 +34,6 @@ local singletonInstance = nil
---@param spawnManager SpawnManager
---@return GlobalStageManager
function GlobalStageManager.new(database, stageConfig, logLevel, spawnManager)
if singletonInstance ~= nil then
return singletonInstance
end
@@ -44,7 +42,7 @@ function GlobalStageManager.new(database, stageConfig, logLevel, spawnManager)
logger:info("Using Stage Log Level: " .. logLevel)
local self = setmetatable({}, GlobalStageManager)
singletonInstance = self
self.database = database
self.stageConfig = stageConfig
self._missionCommandsHelper = MissionCommandsHelper.getOrCreate()
@@ -52,14 +50,15 @@ function GlobalStageManager.new(database, stageConfig, logLevel, spawnManager)
self.logger = logger
if stageConfig.isAutoStages ~= true then
logger:warn("Spearhead will not automatically progress stages due to the given settings. If you manually have implemented this, please ignore this message")
logger:warn(
"Spearhead will not automatically progress stages due to the given settings. If you manually have implemented this, please ignore this message")
end
Events.AddStageNumberChangedListener(self)
for _, stageName in pairs(database:getStagezoneNames()) do
logger:debug("Found stage zone with name: " .. stageName)
local parseResult = self:ParseStageName(stageName)
if parseResult.isValid == false then
logger:warn("Stage zone with name " .. stageName .. " is not valid: " .. parseResult.invalidReason)
@@ -81,12 +80,12 @@ function GlobalStageManager.new(database, stageConfig, logLevel, spawnManager)
stage:AddStageCompleteListener(self)
self._stageRepository:AddStage(stage)
elseif parseResult.stageType == "WaitingStage" then
local waitingStage = WaitingStage.New(database, stageConfig, logger, initData, parseResult.waitingStageSeconds, spawnManager)
local waitingStage = WaitingStage.New(database, stageConfig, logger, initData,
parseResult.waitingStageSeconds, spawnManager)
waitingStage:AddStageCompleteListener(self)
self._stageRepository:AddStage(waitingStage)
end
end
end
singletonInstance = self
@@ -103,11 +102,11 @@ function GlobalStageManager:Start()
local stageLanes = self._stageRepository:getAllStageLanes()
for _, stageLane in pairs(stageLanes) do
local stageLaneIdentifier = stageLane:GetStageLaneIdentifier()
local persistedStage = Persistence.GetActiveStage(stageLaneIdentifier)
if persistedStage then
self.logger:info("Loaded persisted stage " .. persistedStage .. " for lane " .. (stageLaneIdentifier or "default"))
self.logger:info("Loaded persisted stage " ..
persistedStage .. " for lane " .. (stageLaneIdentifier or "default"))
stageLane:SetActiveStageIndex(persistedStage)
Events.PublishStageNumberChanged(persistedStage, stageLaneIdentifier)
else
@@ -139,17 +138,16 @@ end
---@param stageName string
---@return StageNameParseResult
function GlobalStageManager:ParseStageName(stageName)
local split = Util.split_string(stageName, "_")
if Util.tableLength(split) < 3 then
return { isValid = false, invalidReason = "Stage zone with name " .. stageName .. " does not have a order number or valid format" }
return { isValid = false, invalidReason = "Stage zone with name " ..
stageName .. " does not have a order number or valid format" }
end
local typePart = string.lower(split[1])
if typePart == "missionstage" then
local orderNumberString = string.lower(split[2])
---@type StageType
local stageType = "PrimaryStage"
@@ -161,15 +159,16 @@ function GlobalStageManager:ParseStageName(stageName)
local first = orderNumberString:sub(1, 1)
if tonumber(first) == nil then -- first character is the lane identifier only if it's not a number
stageLaneIdentifier = string.lower(first)
orderNumberString = orderNumberString:sub(2)
end
local orderNumber = tonumber(orderNumberString)
if orderNumber == nil then
return { isValid = false, invalidReason = "Stage zone with name " .. stageName .. " does not have a valid order number : " .. orderNumberString }
return { isValid = false, invalidReason = "Stage zone with name " ..
stageName .. " does not have a valid order number : " .. orderNumberString }
end
local stageDisplayName = split[3]
---@type StageNameParseResult
local result = {
@@ -182,18 +181,19 @@ function GlobalStageManager:ParseStageName(stageName)
}
return result
elseif typePart == "waitingstage" then
local stageType = "WaitingStage"
local orderNumberString = split[2]
local orderNumber = tonumber(orderNumberString)
if orderNumber == nil then
return { isValid = false, invalidReason = "Waiting Stage zone with name " .. stageName .. " does not have a valid order number : " .. orderNumberString }
return { isValid = false, invalidReason = "Waiting Stage zone with name " ..
stageName .. " does not have a valid order number : " .. orderNumberString }
end
local waitingSecondsString = split[3]
local waitingSeconds = tonumber(waitingSecondsString)
if waitingSeconds == nil then
return { isValid = false, invalidReason = "Waiting Stage zone with name " .. stageName .. " does not have a valid amount of seconds parameter : " .. waitingSecondsString }
return { isValid = false, invalidReason = "Waiting Stage zone with name " ..
stageName .. " does not have a valid amount of seconds parameter : " .. waitingSecondsString }
end
local stageDisplayName = "Waiting Stage " .. orderNumber
@@ -209,9 +209,9 @@ function GlobalStageManager:ParseStageName(stageName)
waitingStageSeconds = waitingSeconds
}
return result
end
return { isValid = false, invalidReason = "Stage zone with name " .. stageName .. " has an unrecognized type: " .. typePart }
return { isValid = false, invalidReason = "Stage zone with name " ..
stageName .. " has an unrecognized type: " .. typePart }
end
---@param stage Stage
@@ -223,7 +223,8 @@ function GlobalStageManager:OnStageComplete(stage)
local stageLane = self._stageRepository:getStageLane(laneIdentifier)
if not stageLane or not stageLane:IsCurrentStageIndexComplete() then
self.logger:debug("Stage lane " .. (laneIdentifier or "default") .. " is not complete for stage index " .. stageIndex)
self.logger:debug("Stage lane " ..
(laneIdentifier or "default") .. " is not complete for stage index " .. stageIndex)
return
end
@@ -234,7 +235,7 @@ function GlobalStageManager:OnStageComplete(stage)
stageLane:SetActiveStageIndex(nextStageIndex)
if stageLane:IsDefaultStageLane() then
-- COMPLETION IN THE DEFAULT LANE
-- COMPLETION IN THE DEFAULT LANE
-- 1. Check if any lane is at status "BetweenChapters" and the next chapter start is "next stage index"
local allLanes = self._stageRepository:getAllStageLanes()
@@ -242,7 +243,9 @@ function GlobalStageManager:OnStageComplete(stage)
if lane:GetStageLaneIdentifier() ~= StageLane.DefaultLaneKey and lane:GetStageLaneState() == "BetweenChapters" then
local nextChapterStart = lane:GetNextChapterStart()
if nextChapterStart == nextStageIndex then
self.logger:debug("Lane " .. (lane:GetStageLaneIdentifier() or "default") .. " is at chapter start for next stage index " .. nextStageIndex)
self.logger:debug("Lane " ..
(lane:GetStageLaneIdentifier() or "default") ..
" is at chapter start for next stage index " .. nextStageIndex)
Events.PublishStageNumberChanged(nextStageIndex, lane:GetStageLaneIdentifier())
lane:SetActiveStageIndex(nextStageIndex)
end
@@ -256,7 +259,6 @@ function GlobalStageManager:OnStageComplete(stage)
if defaultStageLane and defaultStageLane:GetStageLaneState() == "BetweenChapters" then
local nextChapterStart = defaultStageLane:GetNextChapterStart()
if nextChapterStart == nextStageIndex then
-- 2. Check if all side lanes are at or beyond the next chapter starts
local allLanes = self._stageRepository:getAllStageLanes()
local allSideLanesReady = true
@@ -265,15 +267,19 @@ function GlobalStageManager:OnStageComplete(stage)
-- Check if the lane is at or beyond the next chapter starts when "InChapter" eq "Active"
if lane:GetStageLaneState() == "InChapter" and lane:GetActiveStageIndex() < nextStageIndex then
allSideLanesReady = false
self.logger:debug("Lane " .. (lane:GetStageLaneIdentifier() or "default") .. " is not ready for next stage index " .. nextStageIndex)
self.logger:debug("Lane " ..
(lane:GetStageLaneIdentifier() or "default") ..
" is not ready for next stage index " .. nextStageIndex)
break
-- When in between chapters, check if the next chapter start is less than the next stage index, meaning there is still stages to be completed before the main lane can be activated again
-- When in between chapters, check if the next chapter start is less than the next stage index, meaning there is still stages to be completed before the main lane can be activated again
elseif lane:GetStageLaneState() == "BetweenChapters" then
local nextChapter = lane:GetNextChapterStart()
if nextChapter and nextChapter < nextStageIndex then
allSideLanesReady = false
self.logger:debug("Lane " .. (lane:GetStageLaneIdentifier() or "default") .. " is not ready for next stage index " .. nextStageIndex)
self.logger:debug("Lane " ..
(lane:GetStageLaneIdentifier() or "default") ..
" is not ready for next stage index " .. nextStageIndex)
break
end
end
@@ -281,13 +287,14 @@ function GlobalStageManager:OnStageComplete(stage)
end
if allSideLanesReady then
self.logger:debug("All side lanes are ready for next stage index " .. nextStageIndex .. ", activating default lane")
self.logger:debug("All side lanes are ready for next stage index " ..
nextStageIndex .. ", activating default lane")
Events.PublishStageNumberChanged(nextStageIndex, nil)
defaultStageLane:SetActiveStageIndex(nextStageIndex)
else
self.logger:debug("Not all side lanes are ready for next stage index " .. nextStageIndex .. ", default lane will not be activated")
self.logger:debug("Not all side lanes are ready for next stage index " ..
nextStageIndex .. ", default lane will not be activated")
end
end
end
end
@@ -308,7 +315,6 @@ end
---@public
function GlobalStageManager:OnStageNumberChanged(stageNumber, stageLaneIdentifier)
-- only react on "main" lane changes, ignore other lanes for now
if stageLaneIdentifier ~= nil then return end
@@ -321,7 +327,6 @@ function GlobalStageManager:OnStageNumberChangeComplete(stageNumber, stageLaneId
if stageLaneIdentifier ~= nil then return end
self.logger:debug("Stage number change complete to: " .. tostring(stageNumber))
local groups = {}
for _, player in pairs(DcsUtil.getAllPlayerUnits()) do
@@ -349,80 +354,81 @@ function GlobalStageManager:UpdateDrawings(stageNumber, stageLaneIdentifier)
if laneIdentifier == stageLaneIdentifier then
if stageNumber >= startStage and stageNumber < stopStage then
self.logger:debug("Drawing " .. drawing:GetName() .. " is active for stage number: " .. tostring(stageNumber))
self.logger:debug("Drawing " ..
drawing:GetName() .. " is active for stage number: " .. tostring(stageNumber))
drawing:Draw()
else
self.logger:debug("Drawing " .. drawing:GetName() .. " is not active for stage number: " .. tostring(stageNumber))
self.logger:debug("Drawing " ..
drawing:GetName() .. " is not active for stage number: " .. tostring(stageNumber))
drawing:Remove()
end
end
end
end
function GlobalStageManager:PrintMermaidStage()
local lanes = self._stageRepository:getAllStageLanes()
local nodes = {}
local edges = {}
local stageIndicesByLane = {} -- Track all stage indices per lane
local mainLaneId = "default"
-- Color palette for lanes (26 vibrant colors optimized for dark mode)
local laneColors = {
"#FF6B6B", -- 1: Red
"#4ECDC4", -- 2: Teal
"#45B7D1", -- 3: Blue
"#FFA502", -- 4: Orange
"#95E1D3", -- 5: Mint
"#F38181", -- 6: Coral
"#AA96DA", -- 7: Purple
"#FCBAD3", -- 8: Pink
"#A8E6CF", -- 9: Light green
"#FFD3B6", -- 10: Peach
"#FFAAA5", -- 11: Light red
"#FF8B94", -- 12: Rose
"#FFEAA7", -- 13: Butter
"#DFE6E9", -- 14: Gray
"#00B894", -- 15: Emerald
"#0984E3", -- 16: Cobalt
"#6C5CE7", -- 17: Indigo
"#A29BFE", -- 18: Lavender
"#FD79A8", -- 19: Magenta
"#FDCB6E", -- 20: Gold
"#6C757D", -- 21: Slate
"#20C997", -- 22: Seafoam
"#E74C3C", -- 23: Scarlet
"#3498DB", -- 24: Dodger blue
"#9B59B6", -- 25: Amethyst
"#1ABC9C", -- 26: Turquoise
"#FF6B6B", -- 1: Red
"#4ECDC4", -- 2: Teal
"#45B7D1", -- 3: Blue
"#FFA502", -- 4: Orange
"#95E1D3", -- 5: Mint
"#F38181", -- 6: Coral
"#AA96DA", -- 7: Purple
"#FCBAD3", -- 8: Pink
"#A8E6CF", -- 9: Light green
"#FFD3B6", -- 10: Peach
"#FFAAA5", -- 11: Light red
"#FF8B94", -- 12: Rose
"#FFEAA7", -- 13: Butter
"#DFE6E9", -- 14: Gray
"#00B894", -- 15: Emerald
"#0984E3", -- 16: Cobalt
"#6C5CE7", -- 17: Indigo
"#A29BFE", -- 18: Lavender
"#FD79A8", -- 19: Magenta
"#FDCB6E", -- 20: Gold
"#6C757D", -- 21: Slate
"#20C997", -- 22: Seafoam
"#E74C3C", -- 23: Scarlet
"#3498DB", -- 24: Dodger blue
"#9B59B6", -- 25: Amethyst
"#1ABC9C", -- 26: Turquoise
}
local laneColorMap = {} -- Map lane ID to color
local colorIndex = 1
-- Build nodes and collect all stage indices per lane
for _, lane in ipairs(lanes) do
local laneId = lane:GetStageLaneIdentifier() or "default"
local stageIndices = lane:GetAllStageIndices()
stageIndicesByLane[laneId] = stageIndices
-- Assign color to this lane
laneColorMap[laneId] = laneColors[colorIndex]
colorIndex = colorIndex + 1
if colorIndex > #laneColors then
colorIndex = 1
end
-- Create nodes for each stage index in this lane
for _, stageIndex in ipairs(stageIndices) do
local stages = lane:GetStagesAtIndex(stageIndex)
if stages then
for _, stage in ipairs(stages) do
local nodeId = laneId .. "_" .. stageIndex
local stageType = stage:GetStageType()
local stageName = stage.stageName or stage.zoneName
-- Generate bracket label: [1], [2] for default, [w1], [e2] for other lanes
local bracketLabel
if laneId == "default" then
@@ -430,10 +436,10 @@ function GlobalStageManager:PrintMermaidStage()
else
bracketLabel = "[" .. laneId .. stageIndex .. "]"
end
-- Combined label: [bracket] Name
local label = bracketLabel .. " " .. stageName
-- Different node shapes for different stage types
local nodeShape = "["
local nodeEnd = "]"
@@ -444,14 +450,14 @@ function GlobalStageManager:PrintMermaidStage()
nodeShape = "[["
nodeEnd = "]]"
end
nodes[nodeId] = string.format(' %s%s"%s"%s',
nodes[nodeId] = string.format(' %s%s"%s"%s',
nodeId, nodeShape, label, nodeEnd)
end
end
end
end
-- Add edges for sequential stages within the same lane
for laneId, stageIndices in pairs(stageIndicesByLane) do
for i = 1, #stageIndices - 1 do
@@ -459,7 +465,7 @@ function GlobalStageManager:PrintMermaidStage()
local toIdx = stageIndices[i + 1]
local fromNodeId = laneId .. "_" .. fromIdx
local toNodeId = laneId .. "_" .. toIdx
-- Check if this is a chapter boundary (gap)
local isChapter = false
for _, lane in ipairs(lanes) do
@@ -469,12 +475,12 @@ function GlobalStageManager:PrintMermaidStage()
break
end
end
local label = isChapter and "|chapter|" or ""
table.insert(edges, string.format(' %s -->%s %s', fromNodeId, label, toNodeId))
end
end
-- Get main lane reference
local mainStageLane = nil
for _, lane in ipairs(lanes) do
@@ -483,7 +489,7 @@ function GlobalStageManager:PrintMermaidStage()
break
end
end
-- Add dependencies from DEFAULT lane to SIDE lanes at chapter starts
-- Side lanes activate when main completes the stage BEFORE the chapter start
if mainStageLane then
@@ -491,7 +497,7 @@ function GlobalStageManager:PrintMermaidStage()
if lane:IsDefaultStageLane() == false then
local sideId = lane:GetStageLaneIdentifier()
local sideIndices = stageIndicesByLane[sideId]
-- For each stage in the side lane
for _, stageIdx in ipairs(sideIndices) do
-- Check if this is a chapter start in the side lane
@@ -506,16 +512,16 @@ function GlobalStageManager:PrintMermaidStage()
end
end
end
-- Add dependencies from SIDE lanes gating the MAIN lane at chapter boundaries
-- Main cannot advance to the next chapter until ALL side lanes are at or above that chapter index
if mainStageLane then
local mainIndices = stageIndicesByLane[mainLaneId]
-- For each chapter start in main lane (skip first one)
for i = 2, #mainIndices do
local nextIdx = mainIndices[i]
-- Check if nextIdx is a chapter start (there's a gap before it)
if mainStageLane:IsChapterStart(nextIdx) then
-- Before main can progress to nextIdx, all side lanes must be at >= nextIdx
@@ -523,23 +529,23 @@ function GlobalStageManager:PrintMermaidStage()
if lane:IsDefaultStageLane() == false then
local sideId = lane:GetStageLaneIdentifier()
local sideIndices = stageIndicesByLane[sideId]
-- Find the appropriate gate node for this side lane
-- Use first stage >= nextIdx if it exists, otherwise use the highest stage
local gateStageIdx = nil
for _, idx in ipairs(sideIndices) do
if idx >= nextIdx then
gateStageIdx = idx
break
end
end
-- If no stage >= nextIdx, use the highest stage in this lane
if gateStageIdx == nil and #sideIndices > 0 then
gateStageIdx = sideIndices[#sideIndices]
end
if gateStageIdx then
local fromNodeId = sideId .. "_" .. gateStageIdx
local toNodeId = mainLaneId .. "_" .. nextIdx
@@ -550,25 +556,26 @@ function GlobalStageManager:PrintMermaidStage()
end
end
end
-- Build complete Mermaid diagram
local diagramLines = {"graph TD"}
local diagramLines = { "graph TD" }
-- Add class definitions for each lane with colors and contrasting text
for laneId, color in pairs(laneColorMap) do
table.insert(diagramLines, string.format(' classDef lane_%s fill:%s,stroke:#333,stroke-width:2px,color:#000', laneId, color))
table.insert(diagramLines,
string.format(' classDef lane_%s fill:%s,stroke:#333,stroke-width:2px,color:#000', laneId, color))
end
-- Add nodes
for _, nodeStr in pairs(nodes) do
table.insert(diagramLines, nodeStr)
end
-- Add edges
for _, edgeStr in pairs(edges) do
table.insert(diagramLines, edgeStr)
end
-- Apply classes to nodes
for nodeId in pairs(nodes) do
local laneId = nodeId:match("(.+)_[0-9]+$")
@@ -576,9 +583,10 @@ function GlobalStageManager:PrintMermaidStage()
table.insert(diagramLines, string.format(' class %s lane_%s', nodeId, laneId))
end
end
-- Print as single multi-line message
local diagram = "========== STAGE FLOW DIAGRAM ==========\n" .. table.concat(diagramLines, "\n") .. "\n========== END DIAGRAM =========="
local diagram = "========== STAGE FLOW DIAGRAM ==========\n" ..
table.concat(diagramLines, "\n") .. "\n========== END DIAGRAM =========="
self.logger:info(diagram)
end
@@ -586,10 +594,10 @@ end
---@param stageNumber number
---@param stageLaneIdentifier string? nil for default lan
---@return boolean | nil
GlobalStageManager.isStageComplete = function (stageNumber, stageLaneIdentifier)
GlobalStageManager.isStageComplete = function(stageNumber, stageLaneIdentifier)
if singletonInstance == nil then
Logger.new("StageManager", "INFO"):warn("GlobalStageManager.isStageComplete called before GlobalStageManager was initialized. Returning nil")
Logger.new("StageManager", "INFO"):warn(
"GlobalStageManager.isStageComplete called before GlobalStageManager was initialized. Returning nil")
return nil
end
@@ -41,7 +41,7 @@ end
function SpearheadGroup:SpawnCorpsesOnly()
if self._isSpawned == true then return end
self._spawnManager:SpawnCorpsesOnly(self._groupName)
self._isSpawned = true
@@ -97,7 +97,7 @@ function SpearheadGroup:GetObjects()
local result = {}
if self._isStatic == true then
local staticObject = StaticObject.getByName(self._groupName)
if staticObject then
if staticObject then
table.insert(result, staticObject)
end
else
@@ -105,7 +105,7 @@ function SpearheadGroup:GetObjects()
if not group then return {} end
for _, unit in pairs(group:getUnits()) do
table.insert(result, unit)
end
end
end
return result
end
@@ -123,7 +123,7 @@ function SpearheadGroup:GetAsUnits()
if not group then return {} end
for _, unit in pairs(group:getUnits()) do
table.insert(result, unit)
end
end
return result
end
@@ -133,7 +133,7 @@ function SpearheadGroup:GetAllUnitPositions()
local result = {}
if self._isStatic == true then
local staticObject = StaticObject.getByName(self._groupName)
if staticObject then
if staticObject then
table.insert(result, staticObject:getPoint())
end
else
@@ -141,7 +141,7 @@ function SpearheadGroup:GetAllUnitPositions()
if not group then return {} end
for _, unit in pairs(group:getUnits()) do
table.insert(result, unit:getPoint())
end
end
end
return result
end
@@ -50,16 +50,10 @@ function BlueSam.New(database, logger, zoneName, spawnManager)
---@type table<string, Vec3>
local redUnitsPos = {}
local buildable = false
if self._buildableCrateKilos and self._buildableCrateKilos > 0 then
buildable = true
end
for _, groupName in pairs(blueSamData.groups) do
local spearheadGroup = SpearheadGroup.New(groupName, spawnManager, true)
if spearheadGroup then
if spearheadGroup:GetCoalition() == 2 or spearheadGroup:GetCoalition() == 0 then
table.insert(self._blueGroups, spearheadGroup)
end
@@ -78,7 +72,7 @@ function BlueSam.New(database, logger, zoneName, spawnManager)
--Cleanup units
local cleanup_distance = 5
for blueUnitName, blueUnitPos in pairs(blueUnitsPos) do
for _, blueUnitPos in pairs(blueUnitsPos) do
for redUnitName, redUnitPos in pairs(redUnitsPos) do
local distance = Util.VectorDistance3d(blueUnitPos, redUnitPos)
if distance <= cleanup_distance then
@@ -140,7 +134,9 @@ end
function BlueSam:SpawnGroups()
for unitName, needsCleanup in pairs(self._cleanupUnits) do
DcsUtil.DestroyUnit(unitName)
if needsCleanup then
DcsUtil.DestroyUnit(unitName)
end
end
for _, group in pairs(self._blueGroups) do
@@ -42,11 +42,11 @@ function FarpZone.New(database, logger, zoneName, spawnManager)
self._groups = {}
self._padNames = {}
self._supplyHubs = {}
if farpData then
self._padNames = farpData.padNames
for _, supplyHubName in pairs(farpData.supplyHubNames) do
local supplyHub = SupplyHub.new(database, logger, supplyHubName)
if supplyHub then
@@ -55,7 +55,7 @@ function FarpZone.New(database, logger, zoneName, spawnManager)
end
for _, groupName in pairs(farpData.groups) do
for _, groupName in pairs(farpData.groups) do
local group = SpearheadGroup.New(groupName, spawnManager, true)
table.insert(self._groups, group)
group:Destroy()
@@ -90,7 +90,7 @@ function StageBase.New(databaseManager, logger, airbaseName, spawnManager)
local cleanup_distance = 5
for blueUnitName, blueUnitPos in pairs(blueUnitsPos) do
for _, blueUnitPos in pairs(blueUnitsPos) do
for redUnitName, redUnitPos in pairs(redUnitsPos) do
local distance = Util.VectorDistance3d(blueUnitPos, redUnitPos)
if distance <= cleanup_distance then
@@ -172,7 +172,7 @@ function StageBase:ActivateBlueStage()
else
self:FinaliseBlueStage()
end
end
function StageBase:FinaliseBlueStage()
@@ -42,7 +42,7 @@ function SupplyHub.new(database, logger, zoneName)
end
self._zone = DcsUtil.getZoneByName(zoneName)
self._supplyUnitsTracker = SupplyUnitsTracker.getOrCreate()
self._inZone = {}
self._missionCommandsHelper = MissionCommandsHelper.getOrCreate()
@@ -50,7 +50,7 @@ function BuildableZone:New(targetZone, kilosRequired, crateType, buildableGroup
if params.unpackedKilos >= params.kilos or spawned == false then
return
end
return time + 0.5
end
@@ -76,8 +76,6 @@ function BuildableZone:New(targetZone, kilosRequired, crateType, buildableGroup
self._buildableMission = nil
end
if self._buildableMission == nil then
self._buildableLogger:debug("No buildable mission for zone: " .. targetZone.name)
end
@@ -99,23 +97,11 @@ function BuildableZone:OnBuildingComplete() end
---@field unpackedItems number
---@field unpackedKilos number
---@param mission BuildableMission?
---@param _ BuildableMission?
---@param kilos number
function BuildableZone:OnCrateDroppedOff(mission, kilos)
function BuildableZone:OnCrateDroppedOff(_, kilos)
self._buildableLogger:debug("Crate dropped off in zone: " .. self._targetZone.name)
local timeToUnpack = (kilos / 500) * 15
---@type UnpackCrateParam
local params = {
self = self,
groupsPerKilo = self._groupsPerKilo,
unpackedItems = 0,
kilosPerSecond = kilos/timeToUnpack,
unpackedKilos = 0,
kilos = kilos
}
---@param params UnpackCrateParam
---@param time number
local startUnpackingCrate = function(params, time)
@@ -132,10 +118,21 @@ function BuildableZone:OnCrateDroppedOff(mission, kilos)
params.self:FinaliseCrate(params.kilos)
return
end
return time + 2
end
local timeToUnpack = (kilos / 500) * 15
---@type UnpackCrateParam
local params = {
self = self,
groupsPerKilo = self._groupsPerKilo,
unpackedItems = 0,
kilosPerSecond = kilos/timeToUnpack,
unpackedKilos = 0,
kilos = kilos
}
timer.scheduleFunction(startUnpackingCrate, params, timer.getTime() + 2)
end
@@ -190,7 +187,7 @@ function BuildableZone:SpawnAmount(amount)
return nil
end
for i = 1, amount do
for _ = 1, amount do
local spawned = spawnOne()
if spawned ~= true then
self._buildableLogger:debug("No more groups to spawn in zone: " .. self._targetZone.name)
@@ -49,7 +49,6 @@ end
function StageRepository:AddStage(stage)
local stageLaneIdentifier = stage:GetStageLaneIdentifier() or StageLane.DefaultLaneKey
if self.StageLanes[tostring(stageLaneIdentifier)] == nil then
self.StageLanes[tostring(stageLaneIdentifier)] = StageLane.New(stageLaneIdentifier)
end
@@ -105,7 +105,7 @@ function Stage:superNew(database, stageConfig, logger, initData, stageType, miss
end
self.stageName = initData.stageDisplayName
self._stageType = stageType
self.OnPostStageComplete = nil
self.OnPostBlueActivated = nil
@@ -160,25 +160,25 @@ function Stage:superNew(database, stageConfig, logger, initData, stageType, miss
self._logger:info("Initiating new Stage with name: " .. self.zoneName)
---comment
---@param self Stage
---@param selfA Stage
---@param time number?
self.CheckContinuousAsync = function (self, time)
self:CheckAndUpdateSelf()
if self:IsComplete() == true then
self:NotifyComplete()
self.CheckContinuousAsync = function (selfA, time)
selfA:CheckAndUpdateSelf()
if selfA:IsComplete() == true then
selfA:NotifyComplete()
return nil
end
return time + 20
end
do -- load tables
local missionZones = database:getMissionsForStage(self.zoneName)
self._logger:debug("Found " .. Util.tableLength(missionZones) .. " mission zones for stage: " .. self.zoneName)
for _, missionZone in pairs(missionZones) do
local mission = ZoneMission.new(missionZone, self._missionPriority, database, logger, self, spawnManager)
if mission then
self._db.missionsByCode[mission.code] = mission
@@ -233,7 +233,7 @@ function Stage:superNew(database, stageConfig, logger, initData, stageType, miss
table.insert(self._db.missions, mission)
end
end
else
else
self._logger:info("Using persisted random mission with name: " .. missionName .. " and zone: " .. missionZonePicked)
for _, mission in pairs(missions) do
if string.lower(mission.zoneName) == string.lower(missionZonePicked) then
@@ -284,14 +284,14 @@ end
function Stage:IsComplete()
if self._currentStageState >= StageState.Blue then return true end
for i, mission in pairs(self._db.sams) do
for _, mission in pairs(self._db.sams) do
local state = mission:getState()
if state == "ACTIVE" or state == "NEW" or state =="WAITING" then
return false
end
end
for i, mission in pairs(self._db.missions) do
for _, mission in pairs(self._db.missions) do
local state = mission:getState()
if state == "ACTIVE" or state == "NEW" then
return false
@@ -370,9 +370,9 @@ function Stage:CheckAndUpdateSelf()
local availableMissionsCount = Util.tableLength(getAvailableMissions())
local activeCount = getActiveMissionsCount()
if activeCount < max and availableMissionsCount > 0 then
for i = activeCount+1, max do
for _ = activeCount+1, max do
if availableMissionsCount == 0 then
i = max+1 --exits this loop
break
else
local mission = Util.randomFromList(getAvailableMissions()) --[[@as Mission]]
if mission then
@@ -426,7 +426,7 @@ function Stage:PreActivate()
end
self._currentStageState = StageState.PreActivated
for key, mission in pairs(self._db.sams) do
for _, mission in pairs(self._db.sams) do
if mission then
mission:SpawnInactive()
end
@@ -473,7 +473,7 @@ function Stage:MarkStage()
drawing.colorString = DrawingHelper.ColorTableToColorString(Stage.StageColors.INVISIBLE)
drawing.style = "no line"
end
return drawing
end)
self._customDrawing:Draw()
@@ -585,12 +585,12 @@ function Stage:OnStageNumberChanged(number, stageLaneIdentifier)
if needsBlueActivation() == true then
self:ActivateBlueStage()
end
end
---@param self Stage
---@param mission Mission
Stage.OnMissionComplete = function(self, mission)
---@param _ Mission
Stage.OnMissionComplete = function(self, _)
self:CheckAndUpdateSelf()
end
@@ -645,7 +645,7 @@ function Stage:GetStageStats()
end
end
for _, mission in pairs(self._db.sams) do
for _, _ in pairs(self._db.sams) do
dead = dead + 1
end
@@ -673,13 +673,13 @@ function Stage:ActivateBlueStage()
miscGroup:Spawn()
end
---@param self Stage
local ActivateBlueAsync = function(self)
---@param selfA Stage
local ActivateBlueAsync = function(selfA)
pcall(function()
self:MarkStage()
selfA:MarkStage()
end)
self:ActivateBlueGroups()
selfA:ActivateBlueGroups()
return nil
end
@@ -23,10 +23,9 @@ function ExtraStage.New(database, stageConfig, logger, initData, spawnManager)
self:superNew(database, stageConfig, logger, initData, "ExtraStage", "secondary", spawnManager)
self.OnPostBlueActivated = function (selfStage)
selfStage:MarkStage()
end
self.OnPostStageComplete = function (selfStage)
selfStage:ActivateBlueStage()
end
@@ -48,7 +47,6 @@ function ExtraStage:OnStageNumberChanged(number, stageLaneIdentifier)
return
end
local previousActive = self._activeStage
self._activeStage = number
if self.stageNumber - self._activeStage == self._stageConfig.AmountPreactivateStage then
@@ -25,7 +25,7 @@ function WaitingStage.New(database, stageConfig, logger, initData, waitingSecond
self._startTime = nil
self.CheckContinuousAsync = function (selfA, time)
if selfA:IsComplete() == true then
selfA:NotifyComplete()
return nil
@@ -47,7 +47,7 @@ function WaitingStage:ActivateStage()
timer.scheduleFunction(self.CheckContinuousAsync, self, self._startTime + self._waitTimeSeconds)
end
function WaitingStage:IsComplete()
function WaitingStage:IsComplete()
if timer.getTime() > (self._startTime + self._waitTimeSeconds) then return true end
return false
end
@@ -56,12 +56,12 @@ function WaitingStage:OnStageNumberChanged()
self._logger:debug("Waiting Stage OnStageNumberChanged override")
end
function WaitingStage:MarkStage(stageColor)
function WaitingStage:MarkStage(_)
self._logger:debug("Waiting Stage MarkStage override")
end
function WaitingStage:GetExpectedTime()
return self._startTime + self._waitTimeSeconds
return self._startTime + self._waitTimeSeconds
end
return WaitingStage
@@ -77,7 +77,7 @@ local function MarkupToAll(shapeID, drawID, points, fillColor, lineColor, lineS
end
trigger.action.setMarkupColor(drawID, lineColor)
trigger.action.setMarkupTypeLine(drawID, lineStyle)
end
---@private
@@ -200,7 +200,7 @@ function DrawingHelper.DrawLine(object)
---@type Array<Vec3>
local points = {}
local ids = {}
local ids = {}
for _, point in ipairs(object.points) do
table.insert(points, { x = object.mapX + point.x, y = 0, z = object.mapY + point.y } )
@@ -264,7 +264,7 @@ function DrawingHelper.ColorTableToColorString(rgba)
logger:warn("ColorTableToColorString called with invalid rgba table, returning default color string '0x00000000'")
return "0x00000000"
end
local r = string.format("%02X", math.floor(rgba[1] * 255))
local g = string.format("%02X", math.floor(rgba[2] * 255))
local b = string.format("%02X", math.floor(rgba[3] * 255))
@@ -27,8 +27,8 @@ function BattleManager.New(redGroups, blueGroups, name, logLevel)
self._redGroups = redGroups
self._blueGroups = blueGroups
self._logger:debug("BattleManager created with name: " .. self._name
.. ", red groups: " .. #self._redGroups
self._logger:debug("BattleManager created with name: " .. self._name
.. ", red groups: " .. #self._redGroups
.. ", blue groups: " .. #self._blueGroups)
return self
@@ -131,7 +131,7 @@ function BattleManager:LetUnitsShoot(groups, targetGroups)
expendQtyEnabled = true
}
}
local controller = unit:getController()
if controller then
controller:setTask(shootTask)
@@ -141,7 +141,7 @@ function BattleManager:LetUnitsShoot(groups, targetGroups)
end
end
end
end
end
---@param unit Unit
---@return number
@@ -191,7 +191,7 @@ function BattleManager:IsUnitApplicable(unit)
end
return true
end
---@private
@@ -231,7 +231,7 @@ end
---@param groupHulls Array<Array<Vec2>>
---@return Vec2?
function BattleManager:GetRandomPoint(origin, groupHulls)
local hull = Util.randomFromList(groupHulls) --[[@as Array<Vec2>]]
if not hull then return nil end
local shootPoints = Util.GetTangentHullPointsFromOrigin(hull, origin)
@@ -29,7 +29,7 @@ local SupplyLoadConfig = {
{ centerAngle = 270, angleWidth = 60, minRadius = 15, maxRadius = 50, spacing = 5 },
{ centerAngle = 90, angleWidth = 60, minRadius = 15, maxRadius = 50, spacing = 5 },
}
},
},
["UH-1H"] = {
maxInternalLoad = 2000,
dropZones = {
@@ -236,7 +236,7 @@ function SupplyUnitsTracker:CheckUnitsInZones()
end)
end
end
else
if self._unitInSupplyHub[tostring(unit:getID())] == true then
self._unitInSupplyHub[tostring(unit:getID())] = false
@@ -290,11 +290,11 @@ local cargoCount = 0
---@param crateType CrateType
---@param missionCommandsHelper MissionCommandsHelper
function SupplyUnitsTracker:UnloadRequested(unitID, crateType, missionCommandsHelper)
self._logger:debug("Unload requested for unit: " .. unitID .. " crateType: " .. crateType)
local unit = DcsUtil.GetPlayerUnitByID(unitID)
if unit == nil or unit:isExist() == false then
if unit == nil or unit:isExist() == false then
self._logger:warn("Unload requested for non-existent unit: " .. unitID)
return
end
@@ -303,7 +303,7 @@ function SupplyUnitsTracker:UnloadRequested(unitID, crateType, missionCommandsHe
self._logger:warn("Unload requested for unit with no group: " .. unit:getName())
return
end
local cargoConfig = SupplyConfigHelper.getSupplyConfig(crateType)
if cargoConfig == nil then
@@ -360,11 +360,11 @@ function SupplyUnitsTracker:UnitRequestCrateLoading(groupID, crateType, missionC
end
local unit = group:getUnit(1)
if unit == nil then return end
if unit:isExist() == false then return end
if unit:inAir() == true then
trigger.action.outTextForUnit(unit:getID(), "Land first before crates can be loaded", 10)
return
@@ -381,7 +381,7 @@ function SupplyUnitsTracker:UnitRequestCrateLoading(groupID, crateType, missionC
---@param params LoadCargoParams
local LoadCrateTask = function(params)
local loaded = params.self:TryLoadCrateInUnit(params.unit, params.crateType, params.commandHelper)
if loaded ~= false then
trigger.action.outTextForUnit(unit:getID(), "Loaded crate :" .. params.crateType, 10)
@@ -408,7 +408,7 @@ end
---@param commandHelper MissionCommandsHelper
---@return boolean
function SupplyUnitsTracker:TryLoadCrateInUnit(unit, crateType, commandHelper)
local crateConfigA = SupplyConfigHelper.getSupplyConfig(crateType)
if crateConfigA == nil then
trigger.action.outTextForUnit(unit:getID(), "Invalid crate type: " .. crateType, 5)
@@ -442,7 +442,7 @@ function SupplyUnitsTracker:TryLoadCrateInUnit(unit, crateType, commandHelper)
if group == nil then return false end
local groupID = group:getID()
commandHelper:updateCommandsForGroup(groupID)
return true
end
@@ -460,7 +460,7 @@ function SupplyUnitsTracker:UnitRequestCrateSpawn(groupID, crateType)
return
end
end
end
@@ -482,11 +482,11 @@ function SupplyUnitsTracker:GetBoundingBoxes(foundObject)
if desc == nil or desc.box == nil then
return nil
end
local objPos = foundObject:getPoint()
local box = desc.box
local heading = 0
-- Try to get object heading from position vector's forward direction
-- This works for units and other objects that support getPosition
pcall(function()
@@ -495,13 +495,13 @@ function SupplyUnitsTracker:GetBoundingBoxes(foundObject)
heading = math.atan2(objPosition.x.z, objPosition.x.x)
end
end)
-- For rotated objects, we need to rotate the bounding box
local minX = box.min.x
local maxX = box.max.x
local minZ = box.min.z
local maxZ = box.max.z
-- If object has significant rotation, apply rotation to bbox corners
if math.abs(heading) > 0.1 then
-- Get all 4 corners of bbox in local space
@@ -511,11 +511,11 @@ function SupplyUnitsTracker:GetBoundingBoxes(foundObject)
{maxX, minZ},
{maxX, maxZ}
}
-- Rotate corners and find new min/max
minX, maxX = math.huge, -math.huge
minZ, maxZ = math.huge, -math.huge
for _, corner in ipairs(corners) do
local rotX = corner[1] * math.cos(heading) - corner[2] * math.sin(heading)
local rotZ = corner[1] * math.sin(heading) + corner[2] * math.cos(heading)
@@ -525,7 +525,7 @@ function SupplyUnitsTracker:GetBoundingBoxes(foundObject)
maxZ = math.max(maxZ, rotZ)
end
end
-- Convert relative bbox to world space by adding object position
---@type SupplyUnitsBoundingBox
return {
@@ -549,7 +549,7 @@ end
---@param safetyMargin number Safety margin around objects
---@return boolean True if collision detected
function SupplyUnitsTracker:CheckBBoxCollision(crateBBox, objBBox, safetyMargin)
-- Apply safety margin to object bbox
local objMin = {
x = objBBox.min.x - safetyMargin,
@@ -561,7 +561,7 @@ function SupplyUnitsTracker:CheckBBoxCollision(crateBBox, objBBox, safetyMargin)
y = objBBox.max.y + safetyMargin,
z = objBBox.max.z + safetyMargin
}
-- AABB collision detection
return crateBBox.min.x <= objMax.x and crateBBox.max.x >= objMin.x and
crateBBox.min.y <= objMax.y and crateBBox.max.y >= objMin.y and
@@ -575,7 +575,7 @@ end
function SupplyUnitsTracker:GetCargoPlacePosition(unit, crateTypeName)
local unitPos = unit:getPosition()
-- Get unit's heading from the forward vector (x component)
-- Heading is calculated as: atan2(forward.z, forward.x)
local unitHeading = math.atan2(unitPos.x.z, unitPos.x.x)
@@ -586,14 +586,14 @@ function SupplyUnitsTracker:GetCargoPlacePosition(unit, crateTypeName)
self._logger:error("Could not get bbox for crate type: " .. crateTypeName)
return nil
end
local crateRelativeBBox = crateDesc.box
-- Get drop zone config for this unit
local dropZones = {
{ centerAngle = 180, angleWidth = 60, minRadius = 15, maxRadius = 50, spacing = 5 }
}
if SupplyLoadConfig[unit:getTypeName()] ~= nil then
dropZones = SupplyLoadConfig[unit:getTypeName()].dropZones
end
@@ -646,15 +646,15 @@ function SupplyUnitsTracker:GetCargoPlacePosition(unit, crateTypeName)
for distance = zone.minRadius, zone.maxRadius, zone.spacing do
-- Check multiple positions within the angular slice
local angleStep = math.min(15, zone.angleWidth / 3) -- Divide slice into sections
for angle = minAngle, maxAngle, angleStep do
local radians = math.rad(angle)
-- Calculate position at this angle and distance, relative to unit's heading
-- Angle 0 = forward, 90 = right, 180 = rear, 270 = left
-- Apply unit heading to make angles relative to unit orientation
local worldAngle = radians + unitHeading
local candidateX = unitPos.p.x + distance * math.sin(worldAngle)
local candidateZ = unitPos.p.z + distance * math.cos(worldAngle)
local candidateY = land.getHeight({ x = candidateX, y = candidateZ })
@@ -25,7 +25,7 @@ local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelpe
local BuildableMission = {}
BuildableMission.__index = BuildableMission
---@return string
local function getDefaultBriefing(siteType, coords)
return "We've dispatched forward units to find a proper spot for a new " .. siteType .. "." ..
"\nYou will need to drop off supplies so they can start building." ..
@@ -48,7 +48,7 @@ function BuildableMission.new(database, logger, targetZone, noLandingZone, requi
setmetatable(BuildableMission, Mission)
local self = setmetatable({}, { __index = BuildableMission })
self._targetZone = targetZone
self._database = database
self._requiredKilos = requiredKilos
@@ -75,7 +75,7 @@ function BuildableMission.new(database, logger, targetZone, noLandingZone, requi
end
self.code = tostring(database:GetNewMissionCode())
local splitTargetZoneName = Util.split_string(targetZone.name, "_")
if splitTargetZoneName and splitTargetZoneName[3] and splitTargetZoneName[3] ~= "" then
self.name = splitTargetZoneName[3]
@@ -83,13 +83,6 @@ function BuildableMission.new(database, logger, targetZone, noLandingZone, requi
self.name = "Resupply"
end
local type = "site"
if requiredCrateType == "SAM_CRATE" then
type = "SAM site"
elseif requiredCrateType == "FARP_CRATE" then
type = "FARP"
end
self.zoneName = targetZone.name .. "_supply"
self._logger = logger
self._onCrateDroppedOfListeners = {}
@@ -139,7 +132,7 @@ function BuildableMission:ShowBriefing(groupID)
briefingPart = getDefaultBriefing(siteType, coords)
end
local briefing = "Mission [" .. self.code .. "] " .. self.name ..
local briefing = "Mission [" .. self.code .. "] " .. self.name ..
"\n \n" ..
briefingPart ..
"\n\n" ..
@@ -201,7 +194,7 @@ function BuildableMission:SpawnActive()
local fillColor2 = DrawingHelper.ColorTableToColorString({ 0, 0, 1, 0})
self._dropOffZoneDrawing = CustomDrawing.FromZone(self._dropOffZone, lineColor2, fillColor2, 2, 6)
self._dropOffZoneDrawing:Draw()
---@param selfA BuildableMission
---@param time number
local checkForCrateTasks = function (selfA, time)
@@ -266,8 +259,8 @@ function BuildableMission:CheckCratesInZone()
end
end
end
for _, foundCrate in pairs(foundCrates) do
for _, foundCrate in pairs(foundCrates) do
local crateConfig = SupplyConfigHelper.fromObjectName(foundCrate:getName())
if crateConfig then
self._droppedKilos = self._droppedKilos + crateConfig.weight
@@ -282,7 +275,7 @@ function BuildableMission:CheckCratesInZone()
self:NotifyMissionComplete()
self._state = "COMPLETED"
end
if self._state == "COMPLETED" then
for groupID, markID in pairs(self._markIDsPerGroup) do
if markID then
@@ -42,7 +42,7 @@ function RunwayStrikeMission.new(runway, airbaseName, database, logger, runwayBo
self._runwayZone = self:RunwayToSpearheadZone(runway)
self._repairInProgress = false
self._minKilosForDamage = 100
local sections = self:ToSections(runway, 5)
--[[
+-----------+-----------+-----------+-----------+-----------+
@@ -84,7 +84,6 @@ 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 Util.is3dPointInZone({ x = impactPoint.x, z = impactPoint.y, y = 0 }, zone) then
if section.kilosHit == nil then
@@ -96,7 +95,7 @@ function RunwayStrikeMission:RunwayHit(impactPoint, explosiveMass)
end
---@param selfA RunwayStrikeMission
local updateState = function(selfA, time)
local updateState = function(selfA, _)
selfA:UpdateState()
end
@@ -154,7 +153,7 @@ function RunwayStrikeMission:Draw()
if runwaySection.drawID == nil then
local zone = self:SectionToSpearheadZone(runwaySection)
---@type Free
local drawObject = {
primitiveType = "Polygon",
@@ -213,7 +212,6 @@ function RunwayStrikeMission:StartRepair()
if interval == nil then return nil end
return time + interval
end
timer.scheduleFunction(repairTask, self, timer.getTime() + 5)
end
@@ -211,7 +211,7 @@ end
---@return boolean
function ZoneMission:AllDependenciesMet()
local allDependenciesMet = true
for missionName, value in pairs(self._dependencies) do
for missionName, _ in pairs(self._dependencies) do
if self._parentStage:IsMissionComplete(missionName) == false then
allDependenciesMet = false
self._dependencies[missionName] = false
@@ -503,12 +503,12 @@ end
---@param unit Object
function ZoneMission:MarkLastContact(unit)
if not unit then
if not unit then
self._logger:error("MarkLastContact called with nil unit")
return
end
local point = unit:getPoint()
local point = unit:getPoint()
if not point then
self._logger:error("MarkLastContact called with unit without point")
return
@@ -51,7 +51,7 @@ function Mission.newSuper(self, zoneName, missionName, missionType, missionBrief
self.location = database:GetLocationForMissionZone(zoneName)
self.missionTypeDisplay = self.missionType
self._missionCommandsHelper = MissionCommandsHelper.getOrCreate()
return true, "success"
+22 -23
View File
@@ -77,7 +77,7 @@ do -- INIT DCS_UTIL
do -- INITS ALL TABLES WITH DATA THAT's from the MIZ environment
do --init trigger zones
for i, trigger_zone in pairs(env.mission.triggers.zones) do
for _, trigger_zone in pairs(env.mission.triggers.zones) do
-- reorder verts as they are not ordered correctly in the ME
local verts = {}
if Util.tableLength(trigger_zone.verticies) >= 4 then
@@ -192,7 +192,7 @@ do -- INIT DCS_UTIL
BLUE = 2
}
]] --
local input = string.lower(input)
input = string.lower(input)
if input == 'neutrals' or input == "neutral" or input == "0" then
return DCS_UTIL.Coalition.NEUTRAL
end
@@ -235,7 +235,7 @@ do -- INIT DCS_UTIL
end
end
for index, zone_name in pairs(zone_names) do
for _, zone_name in pairs(zone_names) do
local zone = DCS_UTIL.__trigger_zones[zone_name]
if zone then
zones[#zones + 1] = zone
@@ -247,7 +247,7 @@ do -- INIT DCS_UTIL
local lUnit = units[units_ind]
local unit_pos = lUnit:getPosition().p
local lCat = Object.getCategory(lUnit)
for zone_name, zone in pairs(zones) do
for _, 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
local isInZone = Util.is3dPointInZone(unit_pos, zone)
if isInZone == true then
@@ -313,7 +313,7 @@ do -- INIT DCS_UTIL
function DCS_UTIL.isPositionInZones(x, z, zone_names)
---@type Array<SpearheadTriggerZone>
local zones = {}
for index, zone_name in pairs(zone_names) do
for _, zone_name in pairs(zone_names) do
local zone = DCS_UTIL.__trigger_zones[zone_name]
if zone then
zones[#zones + 1] = zone
@@ -321,7 +321,7 @@ do -- INIT DCS_UTIL
end
local result_zones = {}
for zone_name, zone in pairs(zones) do
for _, zone in pairs(zones) do
if Util.is3dPointInZone({ x = x, z = z, y = 0 }, zone) == true then
result_zones[#result_zones + 1] = zone.name
end
@@ -414,7 +414,7 @@ do -- INIT DCS_UTIL
local height = land.getHeight(location)
local vec3 = { x = location.x, y = height, z = location.y }
local unitType = string.lower(unitType or "")
unitType = string.lower(unitType or "")
local conversionType = config[unitType]
if not conversionType then conversionType = "DDM" end
@@ -471,7 +471,7 @@ do -- INIT DCS_UTIL
lon_hemisphere, math.abs(lon_deg), lon_min_display, lon_sec_display,
altitude * 3,28084)
end
end
---@param zone SpearheadTriggerZone
@@ -574,7 +574,7 @@ do -- INIT DCS_UTIL
local units = {}
for i = 0, 2 do
local players = coalition.getPlayers(i)
for key, unit in pairs(players) do
for _, unit in pairs(players) do
units[#units + 1] = unit
end
end
@@ -617,8 +617,7 @@ do -- INIT DCS_UTIL
end
function DCS_UTIL.CleanCorpse(unitName)
local unitName = "dead_" .. unitName
unitName = "dead_" .. unitName
local object = StaticObject.getByName(unitName)
if object then
@@ -632,11 +631,11 @@ do -- INIT DCS_UTIL
---@field b number
---@field a number
local drawID = 4210
local __drawID = 4210
function DCS_UTIL.GetNextDrawID()
drawID = drawID + 1
return drawID
__drawID = __drawID + 1
return __drawID
end
---@param groupID number
@@ -644,9 +643,9 @@ do -- INIT DCS_UTIL
---@param location Vec3
---@return number markID
function DCS_UTIL.AddMarkToGroup(groupID, text, location)
local drawID = DCS_UTIL.GetNextDrawID()
trigger.action.markToGroup(drawID, text, location, groupID, true, nil)
return drawID
local nextId = DCS_UTIL.GetNextDrawID()
trigger.action.markToGroup(nextId, text, location, groupID, true, nil)
return nextId
end
---comment
@@ -654,9 +653,9 @@ do -- INIT DCS_UTIL
---@param location Vec3
---@return integer
function DCS_UTIL.AddMarkToAll(text, location)
local drawID = DCS_UTIL.GetNextDrawID()
trigger.action.markToAll(drawID, text, location, true, nil)
return drawID
local nextId = DCS_UTIL.GetNextDrawID()
trigger.action.markToAll(nextId, text, location, true, nil)
return nextId
end
---@param markId number
@@ -705,7 +704,7 @@ do -- INIT DCS_UTIL
---@return number? id
function DCS_UTIL.GetNeutralCountry()
for name, id in pairs(country.id) do
for _, id in pairs(country.id) do
if coalition.getCountryCoalition(id) == DCS_UTIL.Coalition.NEUTRAL then
return id
end
@@ -759,7 +758,7 @@ do -- INIT DCS_UTIL
function DCS_UTIL.GetPlayerGroupByGroupID(groupId)
for i = 0, 2 do
local players = coalition.getPlayers(i)
for key, unit in pairs(players) do
for _, unit in pairs(players) do
if unit and unit:isExist() == true then
local group = unit:getGroup()
if group and group:getID() == groupId then
@@ -775,7 +774,7 @@ do -- INIT DCS_UTIL
function DCS_UTIL.GetPlayerUnitByID(unitID)
for i = 0, 2 do
local players = coalition.getPlayers(i)
for key, unit in pairs(players) do
for _, unit in pairs(players) do
if unit and unit:getID() == unitID then
return unit
end
+1 -1
View File
@@ -149,7 +149,7 @@ do -- INIT UTIL
---@param findableTable table
---@return boolean
UTIL.startswithAny = function(str, findableTable)
for key, value in pairs(findableTable) do
for _, value in pairs(findableTable) do
if type(value) == "string" and UTIL.startswith(str, value) then return true end
end
return false