Feature/lanes (#40)
this fixes #35 does not close but starts work partly on #41 Reviewed-on: #40 Co-authored-by: dutchie031 <timrorije@gmail.com>
This commit was merged in pull request #40.
This commit is contained in:
@@ -10,7 +10,6 @@ local MissionCompleteListeners = {}
|
||||
---@class SpearheadAPIInternal
|
||||
---@field notifyMissionComplete fun(zone_name: string)
|
||||
|
||||
|
||||
---@type FullSpearheadAPI
|
||||
SpearheadAPI = {
|
||||
Stages = {
|
||||
@@ -25,12 +24,12 @@ SpearheadAPI = {
|
||||
getCurrentStage = function()
|
||||
return GlobalStageManager.getCurrentStage() or nil
|
||||
end,
|
||||
isStageComplete = function(stageNumber)
|
||||
isStageComplete = function(stageNumber, stageLaneIdentifier)
|
||||
if type(stageNumber) ~= "number" then
|
||||
return false, "stageNumber " .. stageNumber .. " is not a valid number"
|
||||
end
|
||||
|
||||
local isComplete = GlobalStageManager.isStageComplete(stageNumber)
|
||||
local isComplete = GlobalStageManager.isStageComplete(stageNumber, stageLaneIdentifier)
|
||||
if isComplete == nil then
|
||||
return nil, "no stage found with number " .. stageNumber
|
||||
end
|
||||
|
||||
@@ -6,7 +6,7 @@ SpearheadAPI = SpearheadAPI
|
||||
---@class SpearheadStagesAPI
|
||||
---@field changeStage fun(stageNumber: number): boolean, string @Changes the active stage of spearhead. <br/> All other stages will change based on the normal logic. (CAP, BLUE etc.)
|
||||
---@field getCurrentStage fun(): number | nil @Returns the current stange number <br/> Returns nil when the stagenumber was not set before ever, which means Spearhead was not started.
|
||||
---@field isStageComplete fun(stageNumber: number): boolean | nil, string @returns whether a stage (by index) is complete. <br/> @param stageNumber number <br/> @return boolean | nil <br/> @return string
|
||||
---@field isStageComplete fun(stageNumber: number, stageLaneIdentifier: string?): boolean | nil, string @returns whether a stage (by index) is complete. <br/> @param stageNumber number <br/> @param stageLaneIdentifier string? nil for default lane <br/> @return boolean | nil <br/> @return string
|
||||
|
||||
---@class OnMissionCompleteListener
|
||||
---@field onMissionComplete fun(self: OnMissionCompleteListener, zone_name: string) @Called when a mission is completed. @return void
|
||||
@@ -14,4 +14,3 @@ SpearheadAPI = SpearheadAPI
|
||||
---@class MissionAPI
|
||||
---@field addOnMissionCompleteListener fun(listener: OnMissionCompleteListener) @Adds a listener to the mission. <br/> @param listener OnMissionCompleteListener <br/> @return void
|
||||
|
||||
|
||||
|
||||
@@ -392,9 +392,12 @@ function CapBase:CheckAndScheduleIntercept()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
function CapBase:OnStageNumberChanged(number)
|
||||
|
||||
function CapBase:OnStageNumberChanged(number, laneIdentifier)
|
||||
-- only react on "main" lane changes, ignore other lanes for now
|
||||
if laneIdentifier ~= nil then return end
|
||||
|
||||
self.activeStage = number
|
||||
|
||||
if self:IsBaseActiveWhenStageIsActive(number) == true then
|
||||
|
||||
@@ -2,6 +2,9 @@ local SpearheadEvents = require("classes.spearhead_events")
|
||||
local RTBMission = require("classes.capClasses.taskings.RTB")
|
||||
local Util = require("classes.util.Util")
|
||||
local DcsUtil = require("classes.util.DcsUtil")
|
||||
local GlobalConfig = require("classes.configuration.GlobalConfig")
|
||||
local CustomDrawing = require("classes.stageClasses.drawings.CustomDrawing")
|
||||
local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelper")
|
||||
|
||||
---@class AirGroup : OnUnitLostListener
|
||||
---@field protected _logger Logger
|
||||
@@ -12,9 +15,12 @@ local DcsUtil = require("classes.util.DcsUtil")
|
||||
---@field protected _config CapConfig
|
||||
---@field protected _checkLivenessNumber number
|
||||
---@field protected _spawnManager SpawnManager
|
||||
---@field protected _routeDrawing CustomDrawing?
|
||||
local AirGroup = {}
|
||||
AirGroup.__index = AirGroup
|
||||
|
||||
local globalConfig = GlobalConfig.New()
|
||||
|
||||
---@param logger Logger
|
||||
---@param groupName string
|
||||
---@param groupType AirGroupType
|
||||
@@ -28,6 +34,7 @@ function AirGroup:New(groupName, groupType, config, logger, spawnManager)
|
||||
self._config = config
|
||||
self._logger = logger
|
||||
self._spawnManager = spawnManager
|
||||
self._routeDrawings = {}
|
||||
|
||||
local group = Group.getByName(self._groupName)
|
||||
if group then
|
||||
@@ -92,6 +99,27 @@ function AirGroup:SetMissionPrivate(mission)
|
||||
if group and mission then
|
||||
group:getController():setTask(mission)
|
||||
self._logger:debug("mission - Task set for group: " .. self._groupName)
|
||||
|
||||
if globalConfig:isDebugMenuEnabled() == true then
|
||||
-- draw the mission route for debugging purposes
|
||||
if self._routeDrawing then
|
||||
self._routeDrawing:Remove()
|
||||
end
|
||||
|
||||
local points = {}
|
||||
if mission and mission.params and mission.params.route and mission.params.route.points then
|
||||
for _, wp in pairs(mission.params.route.points) do
|
||||
if wp.x and wp.y then
|
||||
table.insert(points, { x = wp.x, y = wp.y })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local colorString = DrawingHelper.ColorTableToColorString({ 1, 0, 0, 1 })
|
||||
self._routeDrawing = CustomDrawing.FromPoints(points, colorString, 5, 2)
|
||||
self._routeDrawing:Draw()
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ local briefingMessageTime = nil
|
||||
---@class GlobalConfig
|
||||
---@field private _briefingTime number
|
||||
---@field private _debugEnabled boolean
|
||||
---@field private _debugMenuEnabled boolean
|
||||
local GlobalConfig = {}
|
||||
GlobalConfig.__index = GlobalConfig;
|
||||
|
||||
@@ -27,6 +28,8 @@ function GlobalConfig.New()
|
||||
self._debugEnabled = false
|
||||
end
|
||||
|
||||
self._debugMenuEnabled = SpearheadConfig.debugMenuEnabled == true
|
||||
|
||||
end
|
||||
|
||||
return self
|
||||
@@ -40,6 +43,10 @@ function GlobalConfig:isDebugEnabled()
|
||||
return self._debugEnabled or false
|
||||
end
|
||||
|
||||
---@return boolean
|
||||
function GlobalConfig:isDebugMenuEnabled()
|
||||
return self._debugMenuEnabled or false
|
||||
end
|
||||
|
||||
return GlobalConfig
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
|
||||
---@class PersistenceConfig
|
||||
---@field private _enabled boolean
|
||||
---@field private directory string?
|
||||
---@field private fileName string?
|
||||
local PersistenceConfig = {}
|
||||
|
||||
function PersistenceConfig.new()
|
||||
local self = setmetatable({}, { __index = PersistenceConfig })
|
||||
|
||||
if not SpearheadConfig then SpearheadConfig = {} end
|
||||
if not SpearheadConfig.Persistence then SpearheadConfig.Persistence = {} end
|
||||
self._enabled = SpearheadConfig.Persistence.enabled == true
|
||||
self.directory = SpearheadConfig.Persistence.directory
|
||||
self.fileName = SpearheadConfig.Persistence.fileName
|
||||
return self
|
||||
end
|
||||
|
||||
function PersistenceConfig:isEnabled()
|
||||
return self._enabled == true
|
||||
end
|
||||
|
||||
function PersistenceConfig:getDirectory()
|
||||
return self.directory
|
||||
end
|
||||
|
||||
function PersistenceConfig:getFileName()
|
||||
return self.fileName
|
||||
end
|
||||
|
||||
return PersistenceConfig
|
||||
@@ -0,0 +1,80 @@
|
||||
local Logger = require("classes.util.Logger")
|
||||
local Util = require("classes.util.Util")
|
||||
local StageRepository = require("classes.stageClasses.StageRepository")
|
||||
|
||||
---@class DebugMenu
|
||||
---@field private _logger Logger
|
||||
---@field private _stageRepository StageRepository
|
||||
local DebugMenu = {}
|
||||
DebugMenu.__index = DebugMenu
|
||||
|
||||
function DebugMenu.new()
|
||||
local self = setmetatable({}, DebugMenu)
|
||||
self._logger = Logger.new("DebugMenu")
|
||||
self._stageRepository = StageRepository.getInstance()
|
||||
return self
|
||||
end
|
||||
|
||||
local menuName = "Spearhead Debug"
|
||||
local debugMenuPath = { [1] = menuName }
|
||||
function DebugMenu:RegisterMenus()
|
||||
|
||||
missionCommands.addSubMenu(menuName, {})
|
||||
|
||||
local refresh = function(params)
|
||||
local selfA = params.self
|
||||
selfA:RefreshMenu()
|
||||
end
|
||||
missionCommands.addCommand("Refresh Menu", debugMenuPath, refresh, { self = self })
|
||||
|
||||
self:AddStageOptions()
|
||||
|
||||
end
|
||||
|
||||
function DebugMenu:RefreshMenu()
|
||||
|
||||
missionCommands.removeItem(debugMenuPath)
|
||||
self:RegisterMenus()
|
||||
end
|
||||
|
||||
function DebugMenu:AddStageOptions()
|
||||
local stageMenuTable = missionCommands.addSubMenu("Stages", debugMenuPath)
|
||||
|
||||
local stageLanes = self._stageRepository:getAllStageLanes()
|
||||
for _, stageLane in pairs(stageLanes) do
|
||||
|
||||
local stages = stageLane:GetStagesAtIndex(stageLane:GetActiveStageIndex())
|
||||
if stages then
|
||||
for _, stage in pairs(stages) do
|
||||
local stageLaneId = stageLane:GetStageLaneIdentifier() or ""
|
||||
local stageIndex = stage:GetStageIndex()
|
||||
local stageName = stage:GetStageName()
|
||||
|
||||
local stageMenuName = stageLaneId .. stageIndex .. "_" .. stageName
|
||||
local currentStageMenuTable = missionCommands.addSubMenu(stageMenuName, stageMenuTable)
|
||||
|
||||
---@class CompleteStageParams
|
||||
---@field self DebugMenu
|
||||
---@field stage Stage
|
||||
|
||||
---@param params CompleteStageParams
|
||||
local completeStage = function(params)
|
||||
local stageA = params.stage
|
||||
local missions = stageA:GetMissions()
|
||||
for _, mission in pairs(missions) do
|
||||
mission:ForceMissionComplete()
|
||||
end
|
||||
end
|
||||
|
||||
---@type CompleteStageParams
|
||||
local params = { self = self, stage = stage }
|
||||
missionCommands.addCommand("Complete Stage", currentStageMenuTable, completeStage, params)
|
||||
|
||||
end
|
||||
else
|
||||
self._logger:info("No stages found for lane: " .. (stageLane:GetStageLaneIdentifier() or "default") .. " at index: " .. (stageLane:GetActiveStageIndex() or "nil"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return DebugMenu
|
||||
@@ -127,7 +127,9 @@ function FleetGroup:new(fleetGroupName, database, logger)
|
||||
end
|
||||
end
|
||||
|
||||
o.OnStageNumberChanged = function(self, number)
|
||||
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]
|
||||
|
||||
@@ -13,7 +13,8 @@ do
|
||||
---@field unitsStates table<string, UnitState>
|
||||
---@field random_missions table<string, MissionState>
|
||||
---@field deliveredKilos table<string, number>
|
||||
---@field activeStage integer|nil
|
||||
---@field activeStage number? required for backwards compatibility
|
||||
---@field activeStageInStageLane table<string, number>
|
||||
|
||||
---@class UnitState
|
||||
---@field isDead boolean
|
||||
@@ -33,7 +34,8 @@ do
|
||||
unitsStates = {},
|
||||
random_missions = {},
|
||||
deliveredKilos = {},
|
||||
activeStage = nil
|
||||
activeStage = nil, -- Backwards compatibility for previous version without lanes
|
||||
activeStageInStageLane = {}
|
||||
}
|
||||
|
||||
local logger = {}
|
||||
@@ -230,12 +232,29 @@ do
|
||||
end
|
||||
|
||||
---Sets the stage in the persistence table
|
||||
---@param stageLane string?
|
||||
---@param stageNumber number
|
||||
Persistence.SetActiveStage = function(stageNumber)
|
||||
tables.activeStage = stageNumber
|
||||
Persistence.SetActiveStage = function(stageLane, stageNumber)
|
||||
stageLane = stageLane or "nil"
|
||||
tables.activeStageInStageLane[stageLane] = stageNumber
|
||||
Persistence._updateRequired = true
|
||||
end
|
||||
|
||||
---Get the active stage as in the persistance file
|
||||
---@param stageLane string?
|
||||
---@return integer|nil
|
||||
Persistence.GetActiveStage = function(stageLane)
|
||||
stageLane = stageLane or "nil"
|
||||
|
||||
-- backwards compatibility for previous versions without stage lanes
|
||||
if stageLane == "nil" and tables.activeStageInStageLane[stageLane] == nil then
|
||||
tables.activeStageInStageLane[stageLane] = tables.activeStage
|
||||
tables.activeStage = nil -- Clear the old activeStage so it's not used or confused later
|
||||
end
|
||||
|
||||
return tables.activeStageInStageLane[stageLane]
|
||||
end
|
||||
|
||||
---comment
|
||||
---@param missionName string
|
||||
---@param pickedZone string
|
||||
@@ -255,14 +274,6 @@ do
|
||||
return tables.random_missions[string.lower(missionName)]
|
||||
end
|
||||
|
||||
---Get the active stage as in the persistance file
|
||||
---@return integer|nil
|
||||
Persistence.GetActiveStage = function()
|
||||
if tables.activeStage then
|
||||
return tables.activeStage
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
---comment
|
||||
---@param zoneName string
|
||||
|
||||
@@ -2,7 +2,7 @@ local Util = require("classes.util.Util")
|
||||
local DcsUtil = require("classes.util.DcsUtil")
|
||||
local MissionEditorWarnings = require("classes.util.MissionEditorWarnings")
|
||||
local MizGroupsManager = require("classes.helpers.MizGroupsManager")
|
||||
local CustomDrawing = require("classes.stageClasses.drawings.CustomDrawing")
|
||||
local StageDrawing = require("classes.stageClasses.drawings.StageDrawing")
|
||||
|
||||
|
||||
---@class DatabaseTables
|
||||
@@ -27,7 +27,7 @@ local CustomDrawing = require("classes.stageClasses.drawings.CustomDrawing")
|
||||
---@field MissionZoneData table<string, MissionZoneData>
|
||||
---@field FarpZoneData table<string,FarpZoneData>
|
||||
---@field missionCodes table<string, boolean>
|
||||
---@field CustomDrawings Array<CustomDrawing>
|
||||
---@field StageDrawings Array<StageDrawing>
|
||||
|
||||
---@class CapRoute
|
||||
---@field zones Array<SpearheadTriggerZone>
|
||||
@@ -107,7 +107,7 @@ function Database.New(Logger)
|
||||
FarpZoneData = {},
|
||||
missionCodes = {},
|
||||
SupplyHubZones = {},
|
||||
CustomDrawings = {}
|
||||
StageDrawings = {}
|
||||
}
|
||||
|
||||
Database.__index = Database
|
||||
@@ -231,8 +231,10 @@ function Database.New(Logger)
|
||||
for key, layer_object in pairs(layer.objects) do
|
||||
if Util.startswith(layer_object.name, "drawing_", true) then
|
||||
local object = layer_object --[[@as DrawingObject]]
|
||||
local customDrawing = CustomDrawing.New(object)
|
||||
table.insert(self._tables.CustomDrawings, customDrawing)
|
||||
local stageDrawing = StageDrawing.New(object)
|
||||
if stageDrawing then
|
||||
table.insert(self._tables.StageDrawings, stageDrawing)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -621,12 +623,12 @@ function Database:loadBlueSamUnits()
|
||||
end
|
||||
end
|
||||
|
||||
---@return Array<CustomDrawing>
|
||||
function Database:getCustomDrawings()
|
||||
if self._tables.CustomDrawings == nil then
|
||||
---@return Array<StageDrawing>
|
||||
function Database:getStageDrawings()
|
||||
if self._tables.StageDrawings == nil then
|
||||
return {}
|
||||
end
|
||||
return self._tables.CustomDrawings
|
||||
return self._tables.StageDrawings
|
||||
end
|
||||
|
||||
---Loads all units, data and briefings
|
||||
|
||||
@@ -29,49 +29,44 @@ do
|
||||
end
|
||||
|
||||
---@class OnStageChangedListener
|
||||
---@field OnStageNumberChanged fun(self:OnStageChangedListener, number:integer)
|
||||
---@field OnStageNumberChanged fun(self:OnStageChangedListener, number:integer, laneIdentifier:string?)
|
||||
|
||||
do -- STAGE NUMBER CHANGED
|
||||
local OnStageNumberChangedListeners = {}
|
||||
local OnStageNumberChangedHandlers = {}
|
||||
---Add a stage zone number changed listener
|
||||
---@param listener OnStageChangedListener object with function OnStageNumberChanged(self, number)
|
||||
---@param listener OnStageChangedListener object with function OnStageNumberChanged(self, number, stageLaneIdentifier)
|
||||
SpearheadEvents.AddStageNumberChangedListener = function(listener)
|
||||
if type(listener) ~= "table" or type(listener.OnStageNumberChanged) ~= "function" then
|
||||
warn("Event handler not of type table/object with function OnStageNumberChanged(self, number, stageLaneIdentifier)")
|
||||
return
|
||||
end
|
||||
table.insert(OnStageNumberChangedListeners, listener)
|
||||
end
|
||||
|
||||
---Add a stage zone number changed listener
|
||||
---@param handler function function(number)
|
||||
SpearheadEvents.AddStageNumberChangedHandler = function(handler)
|
||||
if type(handler) ~= "function" then
|
||||
warn("Event handler not of type function, did you mean to use listener?")
|
||||
return
|
||||
end
|
||||
table.insert(OnStageNumberChangedHandlers, handler)
|
||||
end
|
||||
|
||||
---@class OnStageNumberChangeCompleteListener
|
||||
---@field OnStageNumberChangeComplete fun(self:OnStageNumberChangeCompleteListener, number:integer)
|
||||
---@field OnStageNumberChangeComplete fun(self:OnStageNumberChangeCompleteListener, number:integer, laneIdentifier:string?)
|
||||
|
||||
local OnStageNumberChangeCompleteListeners = {}
|
||||
---@param listener OnStageNumberChangeCompleteListener
|
||||
SpearheadEvents.AddStageNumberChangeCompleteListener = function(listener)
|
||||
if type(listener) ~= "table" or type(listener.OnStageNumberChangeComplete) ~= "function" then
|
||||
warn("Event handler not of type table/object with function OnStageNumberChangeComplete(self, number)")
|
||||
warn("Event handler not of type table/object with function OnStageNumberChangeComplete(self, number, laneIdentifier)")
|
||||
return
|
||||
end
|
||||
table.insert(OnStageNumberChangeCompleteListeners, listener)
|
||||
end
|
||||
|
||||
---@param newStageNumber number
|
||||
SpearheadEvents.PublishStageNumberChanged = function(newStageNumber)
|
||||
---@param laneIdentifier string?
|
||||
SpearheadEvents.PublishStageNumberChanged = function(newStageNumber, laneIdentifier)
|
||||
pcall(function ()
|
||||
Persistence.SetActiveStage(newStageNumber)
|
||||
Persistence.SetActiveStage(laneIdentifier, newStageNumber)
|
||||
end)
|
||||
|
||||
for _, callable in pairs(OnStageNumberChangedListeners) do
|
||||
local succ, err = pcall(function()
|
||||
callable:OnStageNumberChanged(newStageNumber)
|
||||
callable:OnStageNumberChanged(newStageNumber, laneIdentifier)
|
||||
end)
|
||||
if err then
|
||||
logError(err)
|
||||
@@ -79,7 +74,7 @@ do
|
||||
end
|
||||
|
||||
for _, callable in pairs(OnStageNumberChangedHandlers) do
|
||||
local succ, err = pcall(callable, newStageNumber)
|
||||
local succ, err = pcall(callable, newStageNumber, laneIdentifier)
|
||||
if err then
|
||||
logError(err)
|
||||
end
|
||||
@@ -87,7 +82,7 @@ do
|
||||
|
||||
for _, callable in pairs(OnStageNumberChangeCompleteListeners) do
|
||||
local succ, err = pcall(function()
|
||||
callable:OnStageNumberChangeComplete(newStageNumber)
|
||||
callable:OnStageNumberChangeComplete(newStageNumber, laneIdentifier)
|
||||
end)
|
||||
if err then
|
||||
logError(err)
|
||||
|
||||
@@ -3,22 +3,14 @@ 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")
|
||||
local WaitingStage = require("classes.stageClasses.Stages.WaitingStage")
|
||||
local MissionCommandsHelper = require("classes.stageClasses.helpers.MissionCommandsHelper")
|
||||
|
||||
local StagesByName = {}
|
||||
|
||||
---@type table<string, Array<Stage>>
|
||||
local StagesByIndex = {}
|
||||
|
||||
---@type table<string, Array<Stage>>
|
||||
local SideStageByIndex = {}
|
||||
|
||||
---@type table<string, Array<WaitingStage>>
|
||||
local WaitingStagesByIndex = {}
|
||||
local StageRepository = require("classes.stageClasses.StageRepository")
|
||||
local StageLane = require("classes.stageClasses.StageLane")
|
||||
local Persistence = require("classes.persistence.Persistence")
|
||||
|
||||
local currentStage = -99
|
||||
|
||||
@@ -26,25 +18,37 @@ local currentStage = -99
|
||||
---@field private database Database
|
||||
---@field private logger Logger
|
||||
---@field private stageConfig StageConfig
|
||||
---@field private _stageRepository StageRepository
|
||||
---@field private _missionCommandsHelper MissionCommandsHelper
|
||||
local GlobalStageManager = {}
|
||||
GlobalStageManager.__index = GlobalStageManager
|
||||
|
||||
GlobalStageManager.getCurrentStage = function() return currentStage end
|
||||
|
||||
---@type GlobalStageManager
|
||||
local singletonInstance = nil
|
||||
|
||||
---comment
|
||||
---@param database Database
|
||||
---@param stageConfig StageConfig
|
||||
---@param logLevel LogLevel
|
||||
---@param spawnManager SpawnManager
|
||||
---@return nil
|
||||
function GlobalStageManager.NewAndStart(database, stageConfig, logLevel, spawnManager)
|
||||
---@return GlobalStageManager
|
||||
function GlobalStageManager.new(database, stageConfig, logLevel, spawnManager)
|
||||
|
||||
if singletonInstance ~= nil then
|
||||
return singletonInstance
|
||||
end
|
||||
|
||||
local logger = Logger.new("StageManager", logLevel)
|
||||
logger:info("Using Stage Log Level: " .. logLevel)
|
||||
local self = setmetatable({}, GlobalStageManager)
|
||||
singletonInstance = self
|
||||
|
||||
self.database = database
|
||||
self.stageConfig = stageConfig
|
||||
self._missionCommandsHelper = MissionCommandsHelper.getOrCreate()
|
||||
self._stageRepository = StageRepository.getInstance()
|
||||
|
||||
self.logger = logger
|
||||
if stageConfig.isAutoStages ~= true then
|
||||
@@ -55,170 +59,267 @@ function GlobalStageManager.NewAndStart(database, stageConfig, logLevel, spawnMa
|
||||
|
||||
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)
|
||||
else
|
||||
local initData = {
|
||||
stageZoneName = parseResult.stageZoneName,
|
||||
stageNumber = parseResult.orderNumber,
|
||||
stageDisplayName = parseResult.stageDisplayName,
|
||||
stageLaneIdentifier = parseResult.stageLaneIdentifier
|
||||
}
|
||||
|
||||
if Util.startswith(stageName, "missionstage", true) then
|
||||
local valid = true
|
||||
local split = Util.split_string(stageName, "_")
|
||||
if Util.tableLength(split) < 2 then
|
||||
MissionEditorWarnings.Add("Stage zone with name " .. stageName .. " does not have a order number or valid format")
|
||||
valid = false
|
||||
end
|
||||
if parseResult.stageType == "PrimaryStage" then
|
||||
local stage = PrimaryStage.New(database, stageConfig, logger, initData, spawnManager)
|
||||
|
||||
if Util.tableLength(split) < 3 then
|
||||
MissionEditorWarnings.Add("Stage zone with name " .. stageName .. " does not have a stage name")
|
||||
end
|
||||
|
||||
local orderNumber = nil
|
||||
local isSideStage = false
|
||||
if valid == true then
|
||||
local orderNumberString = string.lower(split[2])
|
||||
if Util.startswith(orderNumberString, "x") == true then
|
||||
isSideStage = true
|
||||
|
||||
orderNumberString = string.gsub(orderNumberString, "x", "")
|
||||
orderNumber = tonumber(orderNumberString)
|
||||
else
|
||||
orderNumber = tonumber(split[2])
|
||||
end
|
||||
|
||||
if orderNumber == nil then
|
||||
MissionEditorWarnings.Add("Stage zone with name " .. stageName .. " does not have a valid order number : " .. split[2])
|
||||
valid = false
|
||||
end
|
||||
end
|
||||
|
||||
local stageDisplayName = split[3]
|
||||
local stagelogger = Logger.new(stageName, logLevel)
|
||||
if valid == true and orderNumber then
|
||||
|
||||
---@type StageInitData
|
||||
local initData = {
|
||||
stageDisplayName = stageDisplayName,
|
||||
stageNumber = orderNumber,
|
||||
stageZoneName = stageName,
|
||||
}
|
||||
|
||||
if isSideStage == true then
|
||||
local stage = ExtraStage.New(database, stageConfig, stagelogger, initData, spawnManager)
|
||||
stage:AddStageCompleteListener(self)
|
||||
|
||||
if SideStageByIndex[tostring(orderNumber)] == nil then SideStageByIndex[tostring(orderNumber)] = {} end
|
||||
table.insert(SideStageByIndex[tostring(orderNumber)], stage)
|
||||
else
|
||||
local stage = PrimaryStage.New(database, stageConfig, stagelogger, initData, spawnManager)
|
||||
stage:AddStageCompleteListener(self)
|
||||
|
||||
if StagesByIndex[tostring(orderNumber)] == nil then StagesByIndex[tostring(orderNumber)] = {} end
|
||||
table.insert(StagesByIndex[tostring(orderNumber)], stage)
|
||||
end
|
||||
stage:AddStageCompleteListener(self)
|
||||
self._stageRepository:AddStage(stage)
|
||||
elseif parseResult.stageType == "ExtraStage" then
|
||||
local stage = ExtraStage.New(database, stageConfig, logger, initData, spawnManager)
|
||||
stage:AddStageCompleteListener(self)
|
||||
self._stageRepository:AddStage(stage)
|
||||
elseif parseResult.stageType == "WaitingStage" then
|
||||
local waitingStage = WaitingStage.New(database, stageConfig, logger, initData, parseResult.waitingStageSeconds, spawnManager)
|
||||
waitingStage:AddStageCompleteListener(self)
|
||||
self._stageRepository:AddStage(waitingStage)
|
||||
end
|
||||
end
|
||||
|
||||
if Util.startswith(stageName, "waitingstage", true) then
|
||||
local valid = true
|
||||
|
||||
local split = Util.split_string(stageName, "_")
|
||||
|
||||
if Util.tableLength(split) < 3 then
|
||||
MissionEditorWarnings.Add("Stage zone with name " .. stageName .. " does not have a order number or valid format")
|
||||
valid = false
|
||||
end
|
||||
|
||||
if valid == true then
|
||||
local stageIndexString = split[2]
|
||||
local stageIndex = tonumber(stageIndexString)
|
||||
|
||||
if not stageIndex then
|
||||
MissionEditorWarnings.Add("Stage zone with name " .. stageName .. " does not have a valid order number")
|
||||
valid = false
|
||||
end
|
||||
|
||||
local waitingSecondsString = split[3]
|
||||
local waitingSeconds = tonumber(waitingSecondsString)
|
||||
if not waitingSeconds then
|
||||
MissionEditorWarnings.Add("Waiting Stage zone with name " .. stageName .. " does not have a valid amount of seconds parameter")
|
||||
valid = false
|
||||
end
|
||||
|
||||
if valid == true then
|
||||
local stagelogger = Logger.new(stageName, logLevel)
|
||||
|
||||
---@type WaitingStageInitData
|
||||
local initData = {
|
||||
stageDisplayName = "Waiting Stage " .. stageIndex,
|
||||
stageNumber = stageIndex or -99,
|
||||
stageZoneName = stageName,
|
||||
waitingSeconds = waitingSeconds --[[@as integer]]
|
||||
}
|
||||
local waitingStage = WaitingStage.New(database, stageConfig, stagelogger, initData, spawnManager)
|
||||
|
||||
if WaitingStagesByIndex[tostring(stageIndex)] == nil then
|
||||
WaitingStagesByIndex[tostring(stageIndex)] = {}
|
||||
end
|
||||
table.insert(WaitingStagesByIndex[tostring(stageIndex)], waitingStage)
|
||||
|
||||
waitingStage:AddStageCompleteListener(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
singletonInstance = self
|
||||
return self
|
||||
end
|
||||
|
||||
function GlobalStageManager:Start()
|
||||
self.logger:info("Starting GlobalStageManager")
|
||||
|
||||
local startingStage = self.stageConfig.startingStage or 1
|
||||
local persistenceConfig = PersistenceConfig.new()
|
||||
if persistenceConfig:isEnabled() == true then
|
||||
self.logger:info("Persistence is enabled, loading stage state from persistence")
|
||||
|
||||
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"))
|
||||
stageLane:SetActiveStageIndex(persistedStage)
|
||||
Events.PublishStageNumberChanged(persistedStage, stageLaneIdentifier)
|
||||
else
|
||||
stageLane:SetActiveStageIndex(startingStage)
|
||||
Events.PublishStageNumberChanged(startingStage, stageLaneIdentifier)
|
||||
end
|
||||
end
|
||||
else
|
||||
self.logger:info("Persistence is disabled, starting at stage " .. startingStage)
|
||||
local stageLanes = self._stageRepository:getAllStageLanes()
|
||||
for _, stageLane in pairs(stageLanes) do
|
||||
stageLane:SetActiveStageIndex(startingStage)
|
||||
Events.PublishStageNumberChanged(startingStage, stageLane:GetStageLaneIdentifier())
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@class StageNameParseResult
|
||||
---@field isValid boolean
|
||||
---@field orderNumber integer
|
||||
---@field stageLaneIdentifier string?
|
||||
---@field stageDisplayName string
|
||||
---@field stageZoneName string
|
||||
---@field stageType StageType
|
||||
---@field invalidReason string?
|
||||
---@field waitingStageSeconds integer?
|
||||
|
||||
---@param self GlobalStageManager
|
||||
---@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" }
|
||||
end
|
||||
|
||||
local typePart = string.lower(split[1])
|
||||
|
||||
if typePart == "missionstage" then
|
||||
|
||||
local orderNumberString = string.lower(split[2])
|
||||
---@type StageType
|
||||
local stageType = "PrimaryStage"
|
||||
if Util.endsWith(orderNumberString, "x", true) == true then
|
||||
stageType = "ExtraStage"
|
||||
orderNumberString = orderNumberString:sub(1, -1)
|
||||
end
|
||||
local stageLaneIdentifier = nil
|
||||
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 }
|
||||
end
|
||||
|
||||
local stageDisplayName = split[3]
|
||||
---@type StageNameParseResult
|
||||
local result = {
|
||||
isValid = true,
|
||||
orderNumber = orderNumber,
|
||||
stageLaneIdentifier = stageLaneIdentifier,
|
||||
stageDisplayName = stageDisplayName,
|
||||
stageZoneName = stageName,
|
||||
stageType = stageType
|
||||
}
|
||||
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 }
|
||||
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 }
|
||||
end
|
||||
|
||||
local stageDisplayName = "Waiting Stage " .. orderNumber
|
||||
|
||||
---@type StageNameParseResult
|
||||
local result = {
|
||||
isValid = true,
|
||||
orderNumber = orderNumber,
|
||||
stageLaneIdentifier = nil,
|
||||
stageDisplayName = stageDisplayName,
|
||||
stageZoneName = stageName,
|
||||
stageType = stageType,
|
||||
waitingStageSeconds = waitingSeconds
|
||||
}
|
||||
return result
|
||||
|
||||
end
|
||||
return { isValid = false, invalidReason = "Stage zone with name " .. stageName .. " has an unrecognized type: " .. typePart }
|
||||
end
|
||||
|
||||
---@param stage Stage
|
||||
function GlobalStageManager:OnStageComplete(stage)
|
||||
self.logger:debug("Receiving stage complete event from: " .. stage.zoneName)
|
||||
|
||||
local anyIncomplete = false
|
||||
self.logger:debug("Checking stages for index: " .. tostring(currentStage))
|
||||
for index, stage in pairs(StagesByIndex[tostring(currentStage)]) do
|
||||
if stage:IsComplete() == false then
|
||||
anyIncomplete = true
|
||||
self.logger:debug("Need to wait for Stage " .. stage.zoneName .. " to be completed")
|
||||
else
|
||||
self.logger:debug("Stage verified to be completed: " .. stage.zoneName)
|
||||
end
|
||||
local stageIndex = stage:GetStageIndex()
|
||||
local laneIdentifier = stage:GetStageLaneIdentifier()
|
||||
|
||||
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)
|
||||
return
|
||||
end
|
||||
|
||||
if anyIncomplete == false and self.stageConfig.isAutoStages == true then
|
||||
|
||||
-- CHECK WAITING STAGES
|
||||
local nextStage = currentStage + 1
|
||||
|
||||
if WaitingStagesByIndex[tostring(nextStage)] then
|
||||
for _, waitingStage in pairs(WaitingStagesByIndex[tostring(nextStage)]) do
|
||||
if waitingStage:IsActive() == false then
|
||||
waitingStage:ActivateStage()
|
||||
local nextStageIndex = stageIndex + 1
|
||||
|
||||
Events.PublishStageNumberChanged(nextStageIndex, laneIdentifier)
|
||||
stageLane:SetActiveStageIndex(nextStageIndex)
|
||||
|
||||
if stageLane:IsDefaultStageLane() then
|
||||
-- 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()
|
||||
for _, lane in ipairs(allLanes) do
|
||||
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)
|
||||
Events.PublishStageNumberChanged(nextStageIndex, lane:GetStageLaneIdentifier())
|
||||
lane:SetActiveStageIndex(nextStageIndex)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local anyWaiting = false
|
||||
if WaitingStagesByIndex[tostring(nextStage)] then
|
||||
for _, waitingStage in pairs(WaitingStagesByIndex[tostring(nextStage)]) do
|
||||
if waitingStage:IsComplete() == false then
|
||||
anyWaiting = true
|
||||
else
|
||||
-- COMPLETION IN A SIDE LANE
|
||||
|
||||
-- 1. Check if the default lane is at status "BetweenChapters"
|
||||
local defaultStageLane = self._stageRepository:getStageLane(StageLane.DefaultLaneKey)
|
||||
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
|
||||
for _, lane in ipairs(allLanes) do
|
||||
if lane:GetStageLaneIdentifier() ~= StageLane.DefaultLaneKey then
|
||||
-- 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)
|
||||
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
|
||||
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)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if allSideLanesReady then
|
||||
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")
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
if anyWaiting == false then
|
||||
local newStageNumber = currentStage + 1
|
||||
self.logger:debug("Setting next stage to: " .. tostring(newStageNumber))
|
||||
Events.PublishStageNumberChanged(newStageNumber)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param stageNumber number
|
||||
---@param stageLaneIdentifier string?
|
||||
---@return boolean?
|
||||
function GlobalStageManager:IsStageComplete(stageNumber, stageLaneIdentifier)
|
||||
local stageLane = self._stageRepository:getStageLane(stageLaneIdentifier)
|
||||
if not stageLane then
|
||||
self.logger:warn("Stage lane " .. (stageLaneIdentifier or "default") .. " does not exist")
|
||||
return nil
|
||||
end
|
||||
|
||||
return stageLane:IsStageIndexComplete(stageNumber)
|
||||
end
|
||||
|
||||
---@public
|
||||
function GlobalStageManager:OnStageNumberChanged(stageNumber)
|
||||
function GlobalStageManager:OnStageNumberChanged(stageNumber, stageLaneIdentifier)
|
||||
|
||||
-- only react on "main" lane changes, ignore other lanes for now
|
||||
if stageLaneIdentifier ~= nil then return end
|
||||
|
||||
self.logger:debug("Stage number changed to: " .. tostring(stageNumber))
|
||||
currentStage = stageNumber
|
||||
|
||||
self:UpdateDrawings(stageNumber)
|
||||
end
|
||||
|
||||
function GlobalStageManager:OnStageNumberChangeComplete(stageNumber)
|
||||
function GlobalStageManager:OnStageNumberChangeComplete(stageNumber, stageLaneIdentifier)
|
||||
-- only react on "main" lane changes, ignore other lanes for now
|
||||
if stageLaneIdentifier ~= nil then return end
|
||||
|
||||
self.logger:debug("Stage number change complete to: " .. tostring(stageNumber))
|
||||
|
||||
local groups = {}
|
||||
@@ -236,83 +337,267 @@ function GlobalStageManager:OnStageNumberChangeComplete(stageNumber)
|
||||
end
|
||||
|
||||
---@private
|
||||
function GlobalStageManager:UpdateDrawings(stageNumber)
|
||||
---@param stageNumber number
|
||||
---@param stageLaneIdentifier string?
|
||||
function GlobalStageManager:UpdateDrawings(stageNumber, stageLaneIdentifier)
|
||||
self.logger:debug("Updating custom drawings for stage number: " .. tostring(stageNumber))
|
||||
local drawings = self.database:getCustomDrawings()
|
||||
local drawings = self.database:getStageDrawings()
|
||||
for _, drawing in pairs(drawings) do
|
||||
local startStage, stopStage = drawing:GetStartAndStop()
|
||||
if stageNumber >= startStage and stageNumber < stopStage then
|
||||
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))
|
||||
drawing:Remove()
|
||||
local startStage = drawing:GetStartingStage()
|
||||
local stopStage = drawing:GetRemoveAtStage()
|
||||
local laneIdentifier = drawing:GetStageLaneIdentifier()
|
||||
|
||||
if laneIdentifier == stageLaneIdentifier then
|
||||
if stageNumber >= startStage and stageNumber < stopStage then
|
||||
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))
|
||||
drawing:Remove()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
GlobalStageManager.printFullOverview = function ()
|
||||
function GlobalStageManager:PrintMermaidStage()
|
||||
local lanes = self._stageRepository:getAllStageLanes()
|
||||
|
||||
local logger = Logger.new("StageOverview", "INFO")
|
||||
logger:info("Stage overview:")
|
||||
|
||||
local max = 0
|
||||
local lines = {}
|
||||
for stageIndex, stages in pairs(StagesByIndex) do
|
||||
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
|
||||
}
|
||||
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
|
||||
|
||||
local totalStrike = 0
|
||||
local totalbai = 0
|
||||
local totaldead = 0
|
||||
local totalMissions = 0
|
||||
local totalCas = 0
|
||||
|
||||
for _, stage in pairs(stages) do
|
||||
-- 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)
|
||||
|
||||
local strike, dead, bai, cas = stage:GetStageStats()
|
||||
|
||||
totalStrike = totalStrike + strike
|
||||
totalbai = totalbai + bai
|
||||
totaldead = totaldead + dead
|
||||
totalCas = totalCas + cas
|
||||
totalMissions = totalMissions + strike + dead + bai + cas
|
||||
end
|
||||
|
||||
local index = tonumber(stageIndex)
|
||||
if index then
|
||||
if index > max then
|
||||
max = index
|
||||
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
|
||||
bracketLabel = "[" .. stageIndex .. "]"
|
||||
else
|
||||
bracketLabel = "[" .. laneId .. stageIndex .. "]"
|
||||
end
|
||||
|
||||
-- Combined label: [bracket] Name
|
||||
local label = bracketLabel .. " " .. stageName
|
||||
|
||||
-- Different node shapes for different stage types
|
||||
local nodeShape = "["
|
||||
local nodeEnd = "]"
|
||||
if stageType == "ExtraStage" then
|
||||
nodeShape = "(["
|
||||
nodeEnd = "])"
|
||||
elseif stageType == "WaitingStage" then
|
||||
nodeShape = "[["
|
||||
nodeEnd = "]]"
|
||||
end
|
||||
|
||||
nodes[nodeId] = string.format(' %s%s"%s"%s',
|
||||
nodeId, nodeShape, label, nodeEnd)
|
||||
end
|
||||
end
|
||||
lines[index] ="Stage# " .. tostring(stageIndex).. " | " .. totalStrike .. " strikes | " .. totaldead .. " dead | " .. totalbai .. " BAI | " .. totalCas .. " CAS | Total:" .. totalMissions
|
||||
else
|
||||
logger:warn("Stage index is not a number: " .. stageIndex)
|
||||
end
|
||||
end
|
||||
|
||||
for i = 1, max do
|
||||
if lines[i] then
|
||||
logger:info(lines[i])
|
||||
|
||||
-- Add edges for sequential stages within the same lane
|
||||
for laneId, stageIndices in pairs(stageIndicesByLane) do
|
||||
for i = 1, #stageIndices - 1 do
|
||||
local fromIdx = stageIndices[i]
|
||||
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
|
||||
local normalizedLaneId = lane:GetStageLaneIdentifier() or "default"
|
||||
if normalizedLaneId == laneId then
|
||||
isChapter = lane:IsChapterStart(toIdx)
|
||||
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
|
||||
if lane:IsDefaultStageLane() then
|
||||
mainStageLane = lane
|
||||
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
|
||||
for _, lane in ipairs(lanes) do
|
||||
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
|
||||
if lane:IsChapterStart(stageIdx) then
|
||||
-- Main lane must complete (stageIdx - 1) to activate side lane at stageIdx
|
||||
local fromNodeId = mainLaneId .. "_" .. (stageIdx - 1)
|
||||
local toNodeId = sideId .. "_" .. stageIdx
|
||||
-- Side lane activates when main lane completes the prior stage
|
||||
table.insert(edges, string.format(' %s -->|unlock| %s', fromNodeId, toNodeId))
|
||||
end
|
||||
end
|
||||
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
|
||||
for _, lane in ipairs(lanes) do
|
||||
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
|
||||
table.insert(edges, string.format(' %s -->|gate| %s', fromNodeId, toNodeId))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Build complete Mermaid diagram
|
||||
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))
|
||||
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]+$")
|
||||
if laneId then
|
||||
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 =========="
|
||||
self.logger:info(diagram)
|
||||
end
|
||||
|
||||
---comment
|
||||
---@param stageNumber number
|
||||
---@param stageLaneIdentifier string? nil for default lan
|
||||
---@return boolean | nil
|
||||
GlobalStageManager.isStageComplete = function (stageNumber)
|
||||
|
||||
local stageIndex = tostring(stageNumber)
|
||||
|
||||
if StagesByIndex[stageIndex] == nil then return nil end
|
||||
GlobalStageManager.isStageComplete = function (stageNumber, stageLaneIdentifier)
|
||||
|
||||
for _, stage in ipairs(StagesByIndex[stageIndex]) do
|
||||
if stage:IsComplete() == false then
|
||||
return false
|
||||
end
|
||||
if singletonInstance == nil then
|
||||
Logger.new("StageManager", "INFO"):warn("GlobalStageManager.isStageComplete called before GlobalStageManager was initialized. Returning nil")
|
||||
return nil
|
||||
end
|
||||
|
||||
return true
|
||||
if stageLaneIdentifier then
|
||||
stageLaneIdentifier = string.lower(stageLaneIdentifier)
|
||||
end
|
||||
|
||||
return singletonInstance:IsStageComplete(stageNumber, stageLaneIdentifier)
|
||||
end
|
||||
|
||||
return GlobalStageManager
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
|
||||
|
||||
---@class StageLane
|
||||
---@field private _isDefaultStageLane boolean
|
||||
---@field private _stageLaneIdentifier string
|
||||
---@field private _activeStageIndex number
|
||||
---@field private _stagesInLaneByIndex table<number, Array<Stage>>
|
||||
---@field private _chapterStarts table<number, boolean>?
|
||||
---@field private _stageLaneState StageLaneState
|
||||
---@field private _maxStageIndex number
|
||||
local StageLane = {}
|
||||
StageLane.__index = StageLane
|
||||
|
||||
---@alias StageLaneState
|
||||
---| "InChapter"
|
||||
---| "BetweenChapters"
|
||||
---| "Completed"
|
||||
|
||||
StageLane.DefaultLaneKey = nil
|
||||
|
||||
function StageLane.New(laneIdentifier)
|
||||
|
||||
if not laneIdentifier then
|
||||
laneIdentifier = StageLane.DefaultLaneKey
|
||||
end
|
||||
|
||||
local self = setmetatable({}, { __index = StageLane }) --[[@as StageLane]]
|
||||
self._isDefaultStageLane = laneIdentifier == StageLane.DefaultLaneKey
|
||||
self._stageLaneIdentifier = laneIdentifier
|
||||
self._activeStageIndex = nil
|
||||
self._stagesInLaneByIndex = {}
|
||||
self._chapterStarts = nil
|
||||
self._stageLaneState = "BetweenChapters"
|
||||
return self
|
||||
end
|
||||
|
||||
---@param stage Stage
|
||||
function StageLane:AddStage(stage)
|
||||
local stageIndex = stage:GetStageIndex()
|
||||
if not self._stagesInLaneByIndex[stageIndex] then
|
||||
self._stagesInLaneByIndex[stageIndex] = {}
|
||||
end
|
||||
|
||||
if not self._maxStageIndex or stageIndex > self._maxStageIndex then
|
||||
self._maxStageIndex = stageIndex
|
||||
end
|
||||
|
||||
table.insert(self._stagesInLaneByIndex[stageIndex], stage)
|
||||
end
|
||||
|
||||
---@param stageNumber number
|
||||
---@return boolean?
|
||||
function StageLane:IsStageIndexComplete(stageNumber)
|
||||
local stages = self._stagesInLaneByIndex[stageNumber]
|
||||
if not stages then
|
||||
return nil
|
||||
end
|
||||
|
||||
for _, stage in ipairs(stages) do
|
||||
if not stage:IsComplete() then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
---@return boolean
|
||||
function StageLane:IsCurrentStageIndexComplete()
|
||||
return self:IsStageIndexComplete(self._activeStageIndex) == true
|
||||
end
|
||||
|
||||
---@return string?
|
||||
function StageLane:GetStageLaneIdentifier()
|
||||
return self._stageLaneIdentifier
|
||||
end
|
||||
|
||||
---@return boolean
|
||||
function StageLane:IsDefaultStageLane()
|
||||
return self._isDefaultStageLane
|
||||
end
|
||||
|
||||
---@return number
|
||||
function StageLane:GetActiveStageIndex()
|
||||
return self._activeStageIndex
|
||||
end
|
||||
|
||||
---@param stageNumber number
|
||||
function StageLane:SetActiveStageIndex(stageNumber)
|
||||
|
||||
if self._stagesInLaneByIndex[stageNumber] == nil then
|
||||
self._stageLaneState = "BetweenChapters" -- stage number is not in this lane, so we are between chapters
|
||||
elseif stageNumber > self._maxStageIndex then
|
||||
self._stageLaneState = "Completed" -- stage number is beyond the max stage index
|
||||
else
|
||||
self._stageLaneState = "InChapter"
|
||||
end
|
||||
|
||||
self._activeStageIndex = stageNumber
|
||||
end
|
||||
|
||||
---@return StageLaneState
|
||||
function StageLane:GetStageLaneState()
|
||||
return self._stageLaneState
|
||||
end
|
||||
|
||||
--- Checks if the given stage number is a chapter start. <br>
|
||||
--- Chapter starts are the first StageIndex after a gap in stage numbers. <br>
|
||||
--- For example, if the stage numbers are 1, 2, 3, 5, 6, 7, then stage number 5 is a chapter start because there is a gap between 3 and 5. <br>
|
||||
---@param stageNumber number
|
||||
function StageLane:IsChapterStart(stageNumber)
|
||||
|
||||
if self._chapterStarts == nil then
|
||||
self:FillChapterStarts()
|
||||
end
|
||||
|
||||
return self._chapterStarts[tostring(stageNumber)] == true
|
||||
end
|
||||
|
||||
--- fills chapter starts, chapter starts are where there's a gap in between stage numbers.
|
||||
--- The first stage in a lane is always a chapter start.
|
||||
---@private
|
||||
function StageLane:FillChapterStarts()
|
||||
|
||||
local previousIndex = nil
|
||||
local stageIndices = {}
|
||||
for stageIndex, _ in pairs(self._stagesInLaneByIndex) do
|
||||
table.insert(stageIndices, tonumber(stageIndex))
|
||||
end
|
||||
table.sort(stageIndices)
|
||||
|
||||
self._chapterStarts = self._chapterStarts or {}
|
||||
for _, stageIndex in ipairs(stageIndices) do
|
||||
if previousIndex == nil then
|
||||
-- First stage is always a chapter start
|
||||
self._chapterStarts[tostring(stageIndex)] = true
|
||||
elseif stageIndex > previousIndex + 1 then
|
||||
-- Gap before this stage means it's a chapter start
|
||||
self._chapterStarts[tostring(stageIndex)] = true
|
||||
else
|
||||
-- Consecutive stage, not a chapter start
|
||||
self._chapterStarts[tostring(stageIndex)] = false
|
||||
end
|
||||
previousIndex = stageIndex
|
||||
end
|
||||
end
|
||||
|
||||
---@return Array<number>
|
||||
function StageLane:GetAllStageIndices()
|
||||
local indices = {}
|
||||
for stageIndex, _ in pairs(self._stagesInLaneByIndex) do
|
||||
table.insert(indices, tonumber(stageIndex))
|
||||
end
|
||||
table.sort(indices)
|
||||
return indices
|
||||
end
|
||||
|
||||
---@param stageIndex number
|
||||
---@return Array<Stage>?
|
||||
function StageLane:GetStagesAtIndex(stageIndex)
|
||||
return self._stagesInLaneByIndex[stageIndex]
|
||||
end
|
||||
|
||||
---@return number?
|
||||
function StageLane:GetNextChapterStart()
|
||||
if self._chapterStarts == nil then
|
||||
self:FillChapterStarts()
|
||||
end
|
||||
|
||||
for stageIndex, _ in pairs(self._stagesInLaneByIndex) do
|
||||
if tonumber(stageIndex) > self._activeStageIndex and self._chapterStarts[tostring(stageIndex)] == true then
|
||||
return tonumber(stageIndex)
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
return StageLane
|
||||
@@ -0,0 +1,60 @@
|
||||
local StageLane = require("classes.stageClasses.StageLane")
|
||||
|
||||
---@class StageRepository
|
||||
---@field private StageLanes table<string, StageLane>
|
||||
local StageRepository = {}
|
||||
StageRepository.__index = StageRepository
|
||||
|
||||
local instance = nil
|
||||
|
||||
---@private
|
||||
---@return StageRepository
|
||||
function StageRepository.new()
|
||||
local self = setmetatable({}, StageRepository)
|
||||
self.StageLanes = {}
|
||||
self.StageLanes[tostring(StageLane.DefaultLaneKey)] = StageLane.New(StageLane.DefaultLaneKey)
|
||||
instance = self
|
||||
return self
|
||||
end
|
||||
|
||||
---@return StageRepository
|
||||
StageRepository.getInstance = function()
|
||||
if instance == nil then
|
||||
instance = StageRepository.new()
|
||||
end
|
||||
|
||||
return instance
|
||||
end
|
||||
|
||||
|
||||
---@param laneName string? `nil` for default lane
|
||||
---@return StageLane?
|
||||
function StageRepository:getStageLane(laneName)
|
||||
if not laneName then
|
||||
laneName = StageLane.DefaultLaneKey
|
||||
end
|
||||
return self.StageLanes[tostring(laneName)]
|
||||
end
|
||||
|
||||
---@return Array<StageLane>
|
||||
function StageRepository:getAllStageLanes()
|
||||
local lanes = {}
|
||||
for _, lane in pairs(self.StageLanes) do
|
||||
table.insert(lanes, lane)
|
||||
end
|
||||
return lanes
|
||||
end
|
||||
|
||||
---@param stage Stage
|
||||
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
|
||||
|
||||
self.StageLanes[tostring(stageLaneIdentifier)]:AddStage(stage)
|
||||
end
|
||||
|
||||
return StageRepository
|
||||
@@ -13,12 +13,19 @@ local Events = require("classes.spearhead_events")
|
||||
local GlobalCapManager = require("classes.capClasses.GlobalCapManager")
|
||||
local CustomDrawing = require("classes.stageClasses.drawings.CustomDrawing")
|
||||
local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelper")
|
||||
local StageRepository = require("classes.stageClasses.StageRepository")
|
||||
local StageState = require("classes.stageClasses.Stages.BaseStage.StageState")
|
||||
|
||||
---@alias StageColor
|
||||
---| "RED"
|
||||
---| "BLUE"
|
||||
---| "GRAY"
|
||||
|
||||
---@alias StageType
|
||||
---| "PrimaryStage"
|
||||
---| "ExtraStage"
|
||||
---| "WaitingStage"
|
||||
|
||||
--- @class StageData
|
||||
--- @field stageBriefing string?
|
||||
--- @field missionsByCode table<string, Mission>
|
||||
@@ -33,9 +40,11 @@ local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelpe
|
||||
--- @field supplyHubs Array<SupplyHub>
|
||||
|
||||
--- @class StageInitData
|
||||
--- @field type StageType
|
||||
--- @field stageZoneName string
|
||||
--- @field stageNumber integer
|
||||
--- @field stageDisplayName string
|
||||
--- @field stageLaneIdentifier string?
|
||||
|
||||
|
||||
--- @class StageCompleteListener
|
||||
@@ -45,13 +54,15 @@ local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelpe
|
||||
--- @field zoneName string
|
||||
--- @field stageName string?
|
||||
--- @field stageNumber number
|
||||
--- @field protected _stageLaneIdentifier string?
|
||||
--- @field protected _stageType StageType
|
||||
--- @field protected _currentStageState CurrentStageState
|
||||
--- @field protected _missionCommandsHelper MissionCommandsHelper
|
||||
--- @field protected _missionPriority MissionPriority
|
||||
--- @field protected _database Database
|
||||
--- @field protected _stageRepository StageRepository
|
||||
--- @field protected _db StageData
|
||||
--- @field protected _logger Logger
|
||||
--- @field protected _activeStage integer
|
||||
--- @field protected _stageConfig StageConfig
|
||||
--- @field protected _customDrawing CustomDrawing
|
||||
--- @field protected _spawnedGroups Array<string>
|
||||
@@ -63,14 +74,6 @@ local Stage = {}
|
||||
|
||||
Stage.__index = Stage
|
||||
|
||||
---@enum CurrentStageState
|
||||
Stage.CurrentStageState = {
|
||||
INACTIVE = 0,
|
||||
PREACTIVATED = 1,
|
||||
ACTIVE = 2,
|
||||
BLUE = 3
|
||||
}
|
||||
|
||||
Stage.StageColors = {
|
||||
INVISIBLE = { 0, 0, 0, 0 },
|
||||
RED_ACTIVE = { 1, 0, 0, 0.20 },
|
||||
@@ -86,15 +89,22 @@ Stage.StageColors = {
|
||||
---@param initData StageInitData
|
||||
---@param missionPriority MissionPriority
|
||||
---@param spawnManager SpawnManager
|
||||
---@param stageType StageType
|
||||
---@return Stage
|
||||
function Stage:superNew(database, stageConfig, logger, initData, missionPriority, spawnManager)
|
||||
function Stage:superNew(database, stageConfig, logger, initData, stageType, missionPriority, spawnManager)
|
||||
|
||||
logger:debug("[BaseStage] Initiating stage with name: " .. initData.stageZoneName)
|
||||
self._currentStageState = Stage.CurrentStageState.INACTIVE
|
||||
self._currentStageState = StageState.Inactive
|
||||
self.zoneName = initData.stageZoneName
|
||||
self.stageNumber = initData.stageNumber
|
||||
self._stageRepository = StageRepository.getInstance()
|
||||
if initData.stageLaneIdentifier then
|
||||
self._stageLaneIdentifier = string.lower(initData.stageLaneIdentifier)
|
||||
else
|
||||
self._stageLaneIdentifier = nil
|
||||
end
|
||||
self.stageName = initData.stageDisplayName
|
||||
self._currentStageState = Stage.CurrentStageState.INACTIVE
|
||||
self._stageType = stageType
|
||||
|
||||
self.OnPostStageComplete = nil
|
||||
self.OnPostBlueActivated = nil
|
||||
@@ -115,7 +125,6 @@ function Stage:superNew(database, stageConfig, logger, initData, missionPriority
|
||||
supplyHubs = {}
|
||||
}
|
||||
|
||||
self._activeStage = -99
|
||||
self._stageConfig = stageConfig or {}
|
||||
self._missionCommandsHelper = MissionCommandsHelper.getOrCreate()
|
||||
|
||||
@@ -273,7 +282,7 @@ end
|
||||
|
||||
---@return boolean
|
||||
function Stage:IsComplete()
|
||||
if self._currentStageState == Stage.CurrentStageState.BLUE then return true end
|
||||
if self._currentStageState >= StageState.Blue then return true end
|
||||
|
||||
for i, mission in pairs(self._db.sams) do
|
||||
local state = mission:getState()
|
||||
@@ -288,14 +297,44 @@ function Stage:IsComplete()
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
self._currentStageState = Stage.CurrentStageState.BLUE
|
||||
return true
|
||||
end
|
||||
|
||||
---@return boolean
|
||||
function Stage:IsActive()
|
||||
return self._currentStageState == Stage.CurrentStageState.ACTIVE
|
||||
return self._currentStageState == StageState.Activated
|
||||
end
|
||||
|
||||
---@return StageType
|
||||
function Stage:GetStageType()
|
||||
return self._stageType
|
||||
end
|
||||
|
||||
---@return string?
|
||||
function Stage:GetStageLaneIdentifier()
|
||||
return self._stageLaneIdentifier
|
||||
end
|
||||
|
||||
---@return number
|
||||
function Stage:GetStageIndex()
|
||||
return self.stageNumber
|
||||
end
|
||||
|
||||
---@return string
|
||||
function Stage:GetStageName()
|
||||
return self.stageName
|
||||
end
|
||||
|
||||
function Stage:GetMissions()
|
||||
local missions = {}
|
||||
for _, mission in pairs(self._db.missions) do
|
||||
table.insert(missions, mission)
|
||||
end
|
||||
|
||||
for _, sam in pairs(self._db.sams) do
|
||||
table.insert(missions, sam)
|
||||
end
|
||||
return missions
|
||||
end
|
||||
|
||||
---comment
|
||||
@@ -358,7 +397,7 @@ function Stage:IsMissionComplete(missionName)
|
||||
return mission:getState() == "COMPLETED"
|
||||
end
|
||||
|
||||
---private use only
|
||||
-- private usage only
|
||||
function Stage:NotifyComplete()
|
||||
|
||||
self._logger:info("Stage complete: " .. (self.stageName or self.stageNumber or "unknown"))
|
||||
@@ -381,19 +420,22 @@ end
|
||||
|
||||
---Activates all SAMS, Airbase units etc all at once.
|
||||
function Stage:PreActivate()
|
||||
if self._currentStageState == Stage.CurrentStageState.INACTIVE then
|
||||
self._currentStageState = Stage.CurrentStageState.PREACTIVATED
|
||||
for key, mission in pairs(self._db.sams) do
|
||||
if mission then
|
||||
mission:SpawnInactive()
|
||||
end
|
||||
end
|
||||
|
||||
for _, airbase in pairs(self._db.airbases) do
|
||||
airbase:ActivateRedStage()
|
||||
if self._currentStageState >= StageState.PreActivated then
|
||||
return
|
||||
end
|
||||
|
||||
self._currentStageState = StageState.PreActivated
|
||||
for key, mission in pairs(self._db.sams) do
|
||||
if mission then
|
||||
mission:SpawnInactive()
|
||||
end
|
||||
end
|
||||
|
||||
for _, airbase in pairs(self._db.airbases) do
|
||||
airbase:ActivateRedStage()
|
||||
end
|
||||
|
||||
self:MarkStage()
|
||||
|
||||
end
|
||||
@@ -407,22 +449,22 @@ function Stage:MarkStage()
|
||||
end
|
||||
|
||||
if self._stageConfig.isDrawStagesEnabled == false then return end
|
||||
if self._stageConfig.isDrawPreActivatedEnabled == false and self._currentStageState == Stage.CurrentStageState.PREACTIVATED then
|
||||
if self._stageConfig.isDrawPreActivatedEnabled == false and self._currentStageState == StageState.PreActivated then
|
||||
return
|
||||
end
|
||||
|
||||
if self._customDrawing then
|
||||
self._customDrawing:UpdateDrawingObject(function(drawingObject)
|
||||
local drawing = drawingObject --[[@as Polygon]]
|
||||
if self._currentStageState == Stage.CurrentStageState.ACTIVE then
|
||||
if self._currentStageState == StageState.Activated then
|
||||
drawing.fillColorString = DrawingHelper.ColorTableToColorString(Stage.StageColors.RED_ACTIVE)
|
||||
drawing.colorString = DrawingHelper.ColorTableToColorString(Stage.StageColors.RED_ACTIVE)
|
||||
drawing.style = "dot dash"
|
||||
elseif self._currentStageState == Stage.CurrentStageState.PREACTIVATED then
|
||||
elseif self._currentStageState == StageState.PreActivated then
|
||||
drawing.fillColorString = DrawingHelper.ColorTableToColorString(Stage.StageColors.RED_PREACTIVE)
|
||||
drawing.colorString = DrawingHelper.ColorTableToColorString(Stage.StageColors.RED_PREACTIVE)
|
||||
drawing.style = "no line"
|
||||
elseif self._currentStageState == Stage.CurrentStageState.BLUE then
|
||||
elseif self._currentStageState == StageState.Blue then
|
||||
drawing.fillColorString = DrawingHelper.ColorTableToColorString(Stage.StageColors.BLUE)
|
||||
drawing.colorString = DrawingHelper.ColorTableToColorString(Stage.StageColors.BLUE)
|
||||
drawing.style = "two dash"
|
||||
@@ -440,10 +482,13 @@ function Stage:MarkStage()
|
||||
end
|
||||
|
||||
function Stage:ActivateStage()
|
||||
self._currentStageState = Stage.CurrentStageState.ACTIVE
|
||||
|
||||
if self._currentStageState >= StageState.Activated then
|
||||
return
|
||||
end
|
||||
|
||||
self:PreActivate()
|
||||
|
||||
self._currentStageState = StageState.Activated
|
||||
pcall(function()
|
||||
self:MarkStage()
|
||||
end)
|
||||
@@ -471,6 +516,10 @@ function Stage:ActivateStage()
|
||||
end
|
||||
end
|
||||
|
||||
if self._db and self._db.stageBriefing then
|
||||
self._missionCommandsHelper:AddStageBriefing(self.zoneName, self._db.stageBriefing)
|
||||
end
|
||||
|
||||
timer.scheduleFunction(self.CheckContinuousAsync, self, timer.getTime() + 3)
|
||||
end
|
||||
|
||||
@@ -483,43 +532,60 @@ end
|
||||
---comment
|
||||
---@param self Stage
|
||||
---@param number integer
|
||||
function Stage:OnStageNumberChanged(number)
|
||||
---@param stageLaneIdentifier string?
|
||||
function Stage:OnStageNumberChanged(number, stageLaneIdentifier)
|
||||
|
||||
if self._activeStage == number then --only activate once for a stage
|
||||
return
|
||||
---@return boolean
|
||||
local needsPreActivation = function()
|
||||
if self._stageLaneIdentifier == stageLaneIdentifier then
|
||||
if self.stageNumber <= number + self._stageConfig.AmountPreactivateStage then
|
||||
return true
|
||||
end
|
||||
elseif self._stageLaneIdentifier == nil then
|
||||
--main lane
|
||||
--Check if this stage is a chapter stage
|
||||
if self._stageRepository:getStageLane(self._stageLaneIdentifier):IsChapterStart(self.stageNumber)
|
||||
and self.stageNumber < number + self._stageConfig.AmountPreactivateStage
|
||||
then
|
||||
return true
|
||||
end
|
||||
|
||||
if GlobalCapManager.IsCapActiveWhenZoneIsActive(self.zoneName, number) == true then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local previousActive = self._activeStage
|
||||
self._activeStage = number
|
||||
---@return boolean
|
||||
local needsActivation = function()
|
||||
if self._stageLaneIdentifier == stageLaneIdentifier and self.stageNumber == number then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
if self.stageNumber - self._activeStage == self._stageConfig.AmountPreactivateStage then
|
||||
self._logger:debug("Pre-activating stage: " .. self.zoneName .. " with number: " .. number)
|
||||
self:PreActivate()
|
||||
elseif GlobalCapManager.IsCapActiveWhenZoneIsActive(self.zoneName, number) == true then
|
||||
---@return boolean
|
||||
local needsBlueActivation = function()
|
||||
if self._stageLaneIdentifier == stageLaneIdentifier and self.stageNumber < number then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
if needsPreActivation() == true then
|
||||
self:PreActivate()
|
||||
end
|
||||
|
||||
if number == self.stageNumber then
|
||||
if needsActivation() == true then
|
||||
self:ActivateStage()
|
||||
|
||||
if self._db and self._db.stageBriefing then
|
||||
self._missionCommandsHelper:AddStageBriefing(self.zoneName, self._db.stageBriefing)
|
||||
end
|
||||
end
|
||||
|
||||
if previousActive <= self.stageNumber then
|
||||
if number > self.stageNumber then
|
||||
self:ActivateBlueStage()
|
||||
end
|
||||
if needsBlueActivation() == true then
|
||||
self:ActivateBlueStage()
|
||||
end
|
||||
|
||||
if number > self.stageNumber then
|
||||
self._missionCommandsHelper:RemoveStageBriefing(self.zoneName)
|
||||
end
|
||||
end
|
||||
|
||||
function Stage:GetBriefing()
|
||||
return "Briefing For "
|
||||
|
||||
end
|
||||
|
||||
---@param self Stage
|
||||
@@ -528,7 +594,6 @@ Stage.OnMissionComplete = function(self, mission)
|
||||
self:CheckAndUpdateSelf()
|
||||
end
|
||||
|
||||
|
||||
---private use only
|
||||
function Stage:ActivateBlueGroups()
|
||||
|
||||
@@ -590,8 +655,12 @@ end
|
||||
|
||||
function Stage:ActivateBlueStage()
|
||||
|
||||
if self._currentStageState >= StageState.Blue then
|
||||
return
|
||||
end
|
||||
|
||||
self._logger:debug("Setting stage '" .. Util.toString(self.zoneName) .. "' to blue")
|
||||
self._currentStageState = Stage.CurrentStageState.BLUE
|
||||
self._currentStageState = StageState.Blue
|
||||
for _, mission in pairs(self._db.missions) do
|
||||
mission:SpawnPersistedState()
|
||||
end
|
||||
@@ -615,6 +684,8 @@ function Stage:ActivateBlueStage()
|
||||
return nil
|
||||
end
|
||||
|
||||
self._missionCommandsHelper:RemoveStageBriefing(self.zoneName)
|
||||
|
||||
timer.scheduleFunction(ActivateBlueAsync, self, timer.getTime() + 3)
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
--- StageState enumeration for the current stage state.
|
||||
--- Chronological order, meaning that higher values should represent later stages in the sequence.
|
||||
---@enum CurrentStageState
|
||||
local StageState = {
|
||||
Inactive = 0,
|
||||
PreActivated = 1,
|
||||
Activated = 2,
|
||||
Blue = 3
|
||||
}
|
||||
|
||||
return StageState
|
||||
@@ -19,7 +19,7 @@ function ExtraStage.New(database, stageConfig, logger, initData, spawnManager)
|
||||
setmetatable(ExtraStage, Stage)
|
||||
|
||||
local self = setmetatable({}, { __index = ExtraStage }) --[[@as ExtraStage]]
|
||||
self:superNew(database, stageConfig, logger, initData, "secondary", spawnManager)
|
||||
self:superNew(database, stageConfig, logger, initData, "ExtraStage", "secondary", spawnManager)
|
||||
|
||||
self.OnPostBlueActivated = function (selfStage)
|
||||
|
||||
@@ -36,7 +36,12 @@ end
|
||||
---comment
|
||||
---@param self ExtraStage
|
||||
---@param number integer
|
||||
function ExtraStage:OnStageNumberChanged(number)
|
||||
---@param stageLaneIdentifier string?
|
||||
function ExtraStage:OnStageNumberChanged(number, stageLaneIdentifier)
|
||||
|
||||
if stageLaneIdentifier ~= self._stageLaneIdentifier then
|
||||
return
|
||||
end
|
||||
|
||||
if self._activeStage == number then --only activate once for a stage
|
||||
return
|
||||
|
||||
@@ -18,7 +18,7 @@ function PrimaryStage.New(database, stageConfig, logger, initData, spawnManager)
|
||||
setmetatable(PrimaryStage, Stage)
|
||||
|
||||
local self = setmetatable({}, { __index = PrimaryStage }) --[[@as PrimaryStage]]
|
||||
self:superNew(database, stageConfig, logger, initData, "primary", spawnManager)
|
||||
self:superNew(database, stageConfig, logger, initData, "PrimaryStage", "primary", spawnManager)
|
||||
return self
|
||||
|
||||
end
|
||||
|
||||
@@ -4,28 +4,24 @@ local Stage = require("classes.stageClasses.Stages.BaseStage.Stage")
|
||||
---@field private _waitTimeSeconds integer
|
||||
---@field private _startTime number
|
||||
local WaitingStage = {}
|
||||
|
||||
WaitingStage.__index = WaitingStage
|
||||
|
||||
---@class WaitingStageInitData : StageInitData
|
||||
---@field waitingSeconds integer
|
||||
local WaitingStageInitData = {}
|
||||
|
||||
---comment
|
||||
---@param database Database
|
||||
---@param stageConfig StageConfig
|
||||
---@param logger any
|
||||
---@param initData WaitingStageInitData
|
||||
---@param initData StageInitData
|
||||
---@param spawnManager SpawnManager
|
||||
---@param waitingSeconds integer
|
||||
---@return WaitingStage
|
||||
function WaitingStage.New(database, stageConfig, logger, initData, spawnManager)
|
||||
function WaitingStage.New(database, stageConfig, logger, initData, waitingSeconds, spawnManager)
|
||||
setmetatable(WaitingStage, Stage)
|
||||
|
||||
local self = setmetatable({}, { __index = WaitingStage }) --[[@as WaitingStage]]
|
||||
self:superNew(database, stageConfig, logger, initData, "none", spawnManager)
|
||||
self:superNew(database, stageConfig, logger, initData, "WaitingStage", "none", spawnManager)
|
||||
|
||||
self._waitTimeSeconds = 5
|
||||
if initData.waitingSeconds and initData.waitingSeconds > 5 then self._waitTimeSeconds = initData.waitingSeconds end
|
||||
if waitingSeconds and waitingSeconds > 5 then self._waitTimeSeconds = waitingSeconds end
|
||||
self._startTime = nil
|
||||
|
||||
self.CheckContinuousAsync = function (selfA, time)
|
||||
|
||||
@@ -1,38 +1,58 @@
|
||||
|
||||
local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelper")
|
||||
local Util = require("classes.util.Util")
|
||||
local Logger = require("classes.util.Logger")
|
||||
|
||||
|
||||
local MissionEditorWarnings = require("classes.util.MissionEditorWarnings")
|
||||
local drawingLogger = Logger.new("CustomDrawing")
|
||||
|
||||
|
||||
---@class CustomDrawing
|
||||
---@field private _id integer?
|
||||
---@field private _drawingObject DrawingObject
|
||||
---@field private _startingStage number
|
||||
---@field private _removeAtStage number
|
||||
---@field private _logger Logger
|
||||
---@field protected _IDs Array<integer>?
|
||||
---@field protected _name string?
|
||||
---@field protected _drawingObject DrawingObject
|
||||
local CustomDrawing = {}
|
||||
CustomDrawing.__index = CustomDrawing
|
||||
|
||||
|
||||
---@protected
|
||||
---@param drawingObject DrawingObject
|
||||
---@return CustomDrawing
|
||||
---@return CustomDrawing?
|
||||
function CustomDrawing.New(drawingObject)
|
||||
local self = setmetatable({}, CustomDrawing)
|
||||
self._drawingObject = drawingObject
|
||||
self._id = nil -- initiate ID as nil, when drawn it will be set to the ID returned by DrawingHelper.Draw
|
||||
self._logger = drawingLogger
|
||||
|
||||
local name = drawingObject.name
|
||||
local split = Util.split_string(name or "", "_")
|
||||
local secondPart = split[2] or "1"
|
||||
local splitPart = Util.split_string(secondPart, ":")
|
||||
self._startingStage = tonumber(splitPart[1]) or 1
|
||||
self._removeAtStage = tonumber(splitPart[2]) or math.huge
|
||||
self._IDs = {} -- initiate IDs as an empty array, when drawn it will be set to the IDs returned by DrawingHelper.Draw
|
||||
self._name = drawingObject.name
|
||||
return self
|
||||
end
|
||||
|
||||
---@param points Array<Vec2>
|
||||
---@param colorString string
|
||||
---@param lineStyle LineType
|
||||
---@param lineThickness number
|
||||
---@return CustomDrawing?
|
||||
function CustomDrawing.FromPoints(points, colorString, lineStyle, lineThickness)
|
||||
if points == nil or #points < 3 then
|
||||
drawingLogger:warn("CustomDrawing.FromPoints called with nil or less than 3 points")
|
||||
return nil
|
||||
end
|
||||
|
||||
---@type FreeLine
|
||||
local drawingObject = {
|
||||
mapX = 0,
|
||||
mapY = 0,
|
||||
points = points,
|
||||
lineMode = "free",
|
||||
name = "custom_drawing_" .. tostring(math.random(1000000)),
|
||||
primitiveType = "Polygon",
|
||||
polygonMode = "free",
|
||||
visible = true,
|
||||
style = DrawingHelper.ToLineStyleString(lineStyle),
|
||||
colorString = colorString,
|
||||
thickness = lineThickness,
|
||||
closed = false
|
||||
}
|
||||
|
||||
return CustomDrawing.New(drawingObject)
|
||||
end
|
||||
|
||||
---@param zone SpearheadTriggerZone
|
||||
---@param colorString string
|
||||
---@param fillColorString string
|
||||
@@ -65,7 +85,6 @@ function CustomDrawing.FromZone(zone, colorString, fillColorString, lineStyle, l
|
||||
end
|
||||
|
||||
if zone.zone_type == "Polygon" then
|
||||
|
||||
---@type Free
|
||||
local drawingObject = {
|
||||
mapX = 0,
|
||||
@@ -80,7 +99,7 @@ function CustomDrawing.FromZone(zone, colorString, fillColorString, lineStyle, l
|
||||
fillColorString = fillColorString,
|
||||
thickness = lineThickness,
|
||||
}
|
||||
|
||||
|
||||
return CustomDrawing.New(drawingObject)
|
||||
end
|
||||
end
|
||||
@@ -90,27 +109,25 @@ function CustomDrawing:GetName()
|
||||
return self._drawingObject.name
|
||||
end
|
||||
|
||||
---@return number start
|
||||
---@return number stop
|
||||
function CustomDrawing:GetStartAndStop()
|
||||
return self._startingStage, self._removeAtStage
|
||||
end
|
||||
|
||||
function CustomDrawing:Draw()
|
||||
self._logger:debug("Drawing custom drawing with ID " .. tostring(self._id))
|
||||
drawingLogger:debug("Drawing custom drawing with IDs " .. table.concat(self._IDs, ", "))
|
||||
|
||||
if self._id ~= nil then
|
||||
DrawingHelper.Remove(self._id)
|
||||
if self._IDs ~= nil then
|
||||
for _, id in ipairs(self._IDs) do
|
||||
DrawingHelper.Remove(id)
|
||||
end
|
||||
end
|
||||
|
||||
self._id = DrawingHelper.Draw(self._drawingObject, self._logger)
|
||||
self._IDs = DrawingHelper.Draw(self._drawingObject)
|
||||
end
|
||||
|
||||
function CustomDrawing:Remove()
|
||||
if self._id ~= nil then
|
||||
self._logger:debug("Removing custom drawing with ID " .. tostring(self._id))
|
||||
DrawingHelper.Remove(self._id)
|
||||
self._id = nil
|
||||
if self._IDs ~= nil then
|
||||
for _, id in ipairs(self._IDs) do
|
||||
drawingLogger:debug("Removing custom drawing with ID " .. tostring(id))
|
||||
DrawingHelper.Remove(id)
|
||||
end
|
||||
self._IDs = {}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -119,4 +136,4 @@ function CustomDrawing:UpdateDrawingObject(updateFunc)
|
||||
self._drawingObject = updateFunc(self._drawingObject)
|
||||
end
|
||||
|
||||
return CustomDrawing
|
||||
return CustomDrawing
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
local CustomDrawing = require("classes.stageClasses.drawings.CustomDrawing")
|
||||
local Util = require("classes.util.Util")
|
||||
local MissionEditorWarnings = require("classes.util.MissionEditorWarnings")
|
||||
---@class StageDrawing : CustomDrawing
|
||||
---@field private _startingStage number
|
||||
---@field private _removeAtStage number
|
||||
---@field private _stageLaneIdentifier string?
|
||||
local StageDrawing = {}
|
||||
StageDrawing.__index = StageDrawing
|
||||
setmetatable(StageDrawing, CustomDrawing)
|
||||
|
||||
|
||||
---@param drawingObject DrawingObject
|
||||
---@return StageDrawing?
|
||||
function StageDrawing.New(drawingObject)
|
||||
|
||||
local super = CustomDrawing.New(drawingObject)
|
||||
if not super then
|
||||
return nil
|
||||
end
|
||||
local self = setmetatable(super, StageDrawing) --[[@as StageDrawing]]
|
||||
|
||||
local split = Util.split_string(self._name or "", "_")
|
||||
local secondPart = split[2] or "1"
|
||||
local splitPart = Util.split_string(secondPart, ":")
|
||||
if tonumber(splitPart[1]) == nil then
|
||||
local laneIdentifier = string.sub(splitPart[1], 1, 1)
|
||||
if tonumber(laneIdentifier) == nil then
|
||||
self._stageLaneIdentifier = laneIdentifier
|
||||
end
|
||||
local startNumber = string.sub(splitPart[1], 2)
|
||||
local startNumberVal = tonumber(startNumber)
|
||||
if startNumberVal == nil then
|
||||
MissionEditorWarnings.Add("Start number for " ..
|
||||
self._name .. " is not a valid number: " .. tostring(startNumber))
|
||||
return nil
|
||||
end
|
||||
self._startingStage = startNumberVal
|
||||
else
|
||||
local startNumber = tonumber(splitPart[1])
|
||||
if startNumber == nil then
|
||||
MissionEditorWarnings.Add("Start number for " ..
|
||||
self._name .. " is not a valid number: " .. tostring(splitPart[1]))
|
||||
return nil
|
||||
end
|
||||
self._startingStage = startNumber
|
||||
end
|
||||
|
||||
if tonumber(splitPart[2]) == nil then
|
||||
local laneIdentifier = string.sub(splitPart[2], 1, 1)
|
||||
if tonumber(laneIdentifier) == nil then
|
||||
if self._stageLaneIdentifier and self._stageLaneIdentifier ~= laneIdentifier then
|
||||
MissionEditorWarnings.Add("Lane identifiers do not match for " ..
|
||||
self._name ..
|
||||
": " ..
|
||||
self._stageLaneIdentifier ..
|
||||
" vs " .. laneIdentifier .. ". Will only use stage " .. self._stageLaneIdentifier)
|
||||
end
|
||||
end
|
||||
local stopNumber = string.sub(splitPart[2], 2)
|
||||
local stopNumberVal = tonumber(stopNumber)
|
||||
if stopNumberVal == nil then
|
||||
MissionEditorWarnings.Add("Stop number for " ..
|
||||
self._name .. " is not a valid number: " .. tostring(stopNumber))
|
||||
return nil
|
||||
end
|
||||
self._removeAtStage = stopNumberVal
|
||||
else
|
||||
local stopNumber = tonumber(splitPart[2])
|
||||
if stopNumber == nil then
|
||||
MissionEditorWarnings.Add("Stop number for " ..
|
||||
self._name .. " is not a valid number: " .. tostring(splitPart[2]))
|
||||
return nil
|
||||
end
|
||||
self._removeAtStage = stopNumber
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function StageDrawing:GetStageLaneIdentifier()
|
||||
return self._stageLaneIdentifier
|
||||
end
|
||||
|
||||
function StageDrawing:GetStartingStage()
|
||||
return self._startingStage
|
||||
end
|
||||
|
||||
function StageDrawing:GetRemoveAtStage()
|
||||
return self._removeAtStage
|
||||
end
|
||||
|
||||
return StageDrawing
|
||||
@@ -1,57 +1,46 @@
|
||||
local Util = require("classes.util.Util")
|
||||
local DcsUtil = require("classes.util.DcsUtil")
|
||||
local Logger = require("classes.util.Logger")
|
||||
local GlobalConfig = require("classes.configuration.GlobalConfig")
|
||||
|
||||
---@type LogLevel
|
||||
local level = "INFO"
|
||||
if GlobalConfig:isDebugEnabled() then
|
||||
if GlobalConfig.New():isDebugEnabled() then
|
||||
level = "DEBUG"
|
||||
end
|
||||
|
||||
local drawingLogger = Logger.new("DrawingHelper", level)
|
||||
local logger = Logger.new("DrawingHelper", level)
|
||||
|
||||
---@class DrawingHelper
|
||||
local DrawingHelper = {}
|
||||
DrawingHelper.__index = DrawingHelper
|
||||
|
||||
|
||||
local customDrawingIdIncrementer = 4210
|
||||
|
||||
---@param object DrawingObject
|
||||
---@param logger Logger?
|
||||
---@return integer? id
|
||||
function DrawingHelper.Draw(object, logger)
|
||||
---@return Array<integer> id
|
||||
function DrawingHelper.Draw(object)
|
||||
if object == nil then
|
||||
if logger then
|
||||
logger:warn("DrawingHelper.Draw called with nil object")
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local id = DrawingHelper.GetAndAddId()
|
||||
|
||||
if logger then
|
||||
logger:debug("Drawing object with ID " .. tostring(id) .. " and primitive type: " .. tostring(object.primitiveType))
|
||||
return {}
|
||||
end
|
||||
|
||||
if(object.primitiveType == "Polygon") then
|
||||
DrawingHelper.DrawPolygon(object--[[@as Polygon]], id, logger)
|
||||
return DrawingHelper.DrawPolygon(object--[[@as Polygon]])
|
||||
elseif(object.primitiveType == "Line") then
|
||||
DrawingHelper.DrawLine(object--[[@as Line]], id, logger)
|
||||
return DrawingHelper.DrawLine(object--[[@as Line]])
|
||||
elseif(object.primitiveType == "TextBox") then
|
||||
DrawingHelper.DrawTextBox(object--[[@as TextBox]], id)
|
||||
return DrawingHelper.DrawTextBox(object--[[@as TextBox]])
|
||||
else
|
||||
if logger then
|
||||
logger:warn("Unknown primitive type: " .. tostring(object.primitiveType))
|
||||
end
|
||||
logger:warn("Unknown primitive type: " .. tostring(object.primitiveType))
|
||||
end
|
||||
|
||||
return id
|
||||
return {}
|
||||
end
|
||||
|
||||
function DrawingHelper.GetAndAddId()
|
||||
customDrawingIdIncrementer = customDrawingIdIncrementer + 1
|
||||
return customDrawingIdIncrementer
|
||||
return DcsUtil.GetNextDrawID()
|
||||
end
|
||||
|
||||
---@private
|
||||
@@ -62,8 +51,7 @@ end
|
||||
---@param lineColor table
|
||||
---@param lineStyle LineType
|
||||
---@param lineThickness number
|
||||
---@param logger Logger?
|
||||
local function MarkupToAll(shapeID, drawID, points, fillColor, lineColor, lineStyle, lineThickness, logger)
|
||||
local function MarkupToAll(shapeID, drawID, points, fillColor, lineColor, lineStyle, lineThickness)
|
||||
|
||||
if lineThickness == nil or lineThickness <= 0 then
|
||||
lineStyle = 0
|
||||
@@ -76,23 +64,19 @@ local function MarkupToAll(shapeID, drawID, points, fillColor, lineColor, lineS
|
||||
end
|
||||
functionString = functionString .. "{0,1,0,1}, {0,1,0,1}, " .. lineStyle .. ")"
|
||||
|
||||
if logger then
|
||||
logger:debug("Drawing complex drawing with ID " .. tostring(drawID) .. " and function string: " .. functionString)
|
||||
end
|
||||
logger:debug("Drawing complex drawing with ID " .. tostring(drawID) .. " and function string: " .. 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
|
||||
|
||||
if logger then
|
||||
logger:debug("Drawing with fill color: " .. table.concat(fillColor, ",") .. " and line color: " .. table.concat(lineColor, ",") .. " and line style: " .. tostring(lineStyle) .. " and line thickness: " .. tostring(lineThickness))
|
||||
logger:error("Something failed when drawing complex drawing" .. err)
|
||||
end
|
||||
|
||||
trigger.action.setMarkupColorFill(drawID, fillColor)
|
||||
if fillColor then
|
||||
trigger.action.setMarkupColorFill(drawID, fillColor)
|
||||
end
|
||||
trigger.action.setMarkupColor(drawID, lineColor)
|
||||
trigger.action.setMarkupTypeLine(drawID, lineStyle)
|
||||
|
||||
@@ -100,13 +84,14 @@ end
|
||||
|
||||
---@private
|
||||
---@param object Polygon
|
||||
---@param id integer
|
||||
---@param logger Logger?
|
||||
function DrawingHelper.DrawPolygon(object, id, logger)
|
||||
---@return Array<integer>
|
||||
function DrawingHelper.DrawPolygon(object)
|
||||
if object == nil then
|
||||
return
|
||||
return {}
|
||||
end
|
||||
|
||||
|
||||
local id = DrawingHelper.GetAndAddId()
|
||||
|
||||
---@param circle Circle
|
||||
local function DrawCircle(circle)
|
||||
local vec3 = { x = circle.mapX, y = 0, z = circle.mapY }
|
||||
@@ -115,15 +100,15 @@ function DrawingHelper.DrawPolygon(object, id, logger)
|
||||
local style = DrawingHelper.ToLineStyleInteger(circle.style)
|
||||
trigger.action.circleToAll(-1, id, vec3, circle.radius, colorString, fillColor, style, true)
|
||||
end
|
||||
|
||||
|
||||
---@param oval Oval
|
||||
---@param logger Logger?
|
||||
local function DrawOval(oval, logger)
|
||||
---@return integer?, Array<CustomDrawing>?
|
||||
local function DrawOval(oval)
|
||||
---@type Array<Vec3>
|
||||
local points = {}
|
||||
local pointsNo = 30
|
||||
local angleStep = (2 * math.pi) / pointsNo
|
||||
|
||||
|
||||
local fillColor = DrawingHelper.ColorToColorTable(oval.fillColorString)
|
||||
local color = DrawingHelper.ColorToColorTable(oval.colorString)
|
||||
local lineStyle = DrawingHelper.ToLineStyleInteger(oval.style)
|
||||
@@ -134,12 +119,11 @@ function DrawingHelper.DrawPolygon(object, id, logger)
|
||||
local y = oval.mapY + (oval.r2 * math.sin(angle))
|
||||
table.insert(points, { x = x, y = 0, z = y } )
|
||||
end
|
||||
MarkupToAll(7, id, points, fillColor, color, lineStyle, oval.thickness, logger)
|
||||
MarkupToAll(7, id, points, fillColor, color, lineStyle, oval.thickness)
|
||||
end
|
||||
|
||||
---@param free Free
|
||||
---@param logger Logger?
|
||||
local function DrawFree(free, logger)
|
||||
local function DrawFree(free)
|
||||
local fillColor = DrawingHelper.ColorToColorTable(free.fillColorString)
|
||||
local color = DrawingHelper.ColorToColorTable(free.colorString)
|
||||
local lineStyle = DrawingHelper.ToLineStyleInteger(free.style)
|
||||
@@ -163,7 +147,7 @@ function DrawingHelper.DrawPolygon(object, id, logger)
|
||||
end
|
||||
end
|
||||
|
||||
MarkupToAll(7, id, points, fillColor, color, lineStyle, free.thickness, logger)
|
||||
MarkupToAll(7, id, points, fillColor, color, lineStyle, free.thickness)
|
||||
end
|
||||
|
||||
---@param rect Rect
|
||||
@@ -175,19 +159,16 @@ function DrawingHelper.DrawPolygon(object, id, logger)
|
||||
local pointA = { x = rect.mapX, y = 0, z = rect.mapY }
|
||||
local pointB = { x = rect.mapX + rect.width, y = 0, z = rect.mapY + rect.height }
|
||||
trigger.action.rectToAll(-1, id, pointA, pointB, color, fillColor, lineStyle, true)
|
||||
|
||||
end
|
||||
|
||||
---@param arrow Arrow
|
||||
---@param logger Logger?
|
||||
local function DrawArrow(arrow, logger)
|
||||
local function DrawArrow(arrow)
|
||||
local fillColor = DrawingHelper.ColorToColorTable(arrow.fillColorString)
|
||||
local color = DrawingHelper.ColorToColorTable(arrow.colorString)
|
||||
local lineStyle = DrawingHelper.ToLineStyleInteger(arrow.style)
|
||||
|
||||
if logger then
|
||||
logger:debug("Drawing arrow with start point: " .. tostring(arrow.mapX) .. ", " .. tostring(arrow.mapY) .. " and angle: " .. tostring(arrow.angle) .. " and length: " .. tostring(arrow.length))
|
||||
end
|
||||
logger:debug("Drawing arrow with start point: " .. tostring(arrow.mapX) .. ", " .. tostring(arrow.mapY) .. " and angle: " .. tostring(arrow.angle) .. " and length: " .. tostring(arrow.length))
|
||||
|
||||
|
||||
local endPoint = { x = arrow.mapX, y = 0, z = arrow.mapY }
|
||||
local rad = math.rad(arrow.angle or 0)
|
||||
@@ -203,44 +184,56 @@ function DrawingHelper.DrawPolygon(object, id, logger)
|
||||
if object.polygonMode == "circle" then
|
||||
DrawCircle(object--[[@as Circle]])
|
||||
elseif object.polygonMode == "oval" then
|
||||
DrawOval(object--[[@as Oval]], logger)
|
||||
DrawOval(object--[[@as Oval]])
|
||||
elseif object.polygonMode == "free" then
|
||||
DrawFree(object--[[@as Free]], logger)
|
||||
DrawFree(object--[[@as Free]])
|
||||
elseif object.polygonMode == "rect" then
|
||||
DrawRect(object--[[@as Rect]])
|
||||
elseif object.polygonMode == "arrow" then
|
||||
DrawArrow(object--[[@as Arrow]], logger)
|
||||
DrawArrow(object--[[@as Arrow]])
|
||||
end
|
||||
return {id}
|
||||
end
|
||||
|
||||
---@private
|
||||
---@param object Line
|
||||
---@param id integer
|
||||
---@param logger Logger?
|
||||
function DrawingHelper.DrawLine(object, id, logger)
|
||||
---@return Array<integer>
|
||||
function DrawingHelper.DrawLine(object)
|
||||
|
||||
---@type Array<Vec3>
|
||||
local points = {}
|
||||
local ids = {}
|
||||
|
||||
for _, point in ipairs(object.points) do
|
||||
table.insert(points, { x = point.x, y = 0, z = point.y } )
|
||||
table.insert(points, { x = object.mapX + point.x, y = 0, z = object.mapY + point.y } )
|
||||
end
|
||||
|
||||
local color = DrawingHelper.ColorToColorTable(object.colorString)
|
||||
local lineStyle = DrawingHelper.ToLineStyleInteger(object.style)
|
||||
MarkupToAll(1, id, points, color, color, lineStyle, object.thickness, logger)
|
||||
|
||||
for i = 1, #points - 1 do
|
||||
local id = DrawingHelper.GetAndAddId()
|
||||
trigger.action.lineToAll(-1, id, points[i], points[i + 1], color, lineStyle, true)
|
||||
table.insert(ids, id)
|
||||
end
|
||||
return ids
|
||||
end
|
||||
|
||||
---@private
|
||||
---@param object TextBox
|
||||
---@param id integer
|
||||
function DrawingHelper.DrawTextBox(object, id)
|
||||
---@return Array<integer>
|
||||
function DrawingHelper.DrawTextBox(object)
|
||||
|
||||
local id = DrawingHelper.GetAndAddId()
|
||||
|
||||
trigger.action.textToAll(-1, id, { x= object.mapX, y = 0, z = object.mapY },
|
||||
DrawingHelper.ColorToColorTable(object.colorString),
|
||||
DrawingHelper.ColorToColorTable(object.fillColorString),
|
||||
object.fontSize or 12,
|
||||
true,
|
||||
object.text or "")
|
||||
|
||||
return {id}
|
||||
end
|
||||
|
||||
function DrawingHelper.Remove(id)
|
||||
@@ -252,7 +245,7 @@ end
|
||||
function DrawingHelper.ColorToColorTable(hexStr)
|
||||
|
||||
if hexStr == nil then
|
||||
drawingLogger:warn("ColorToColorTable called with nil hexStr, returning default color {0, 0, 0, 0}")
|
||||
logger:warn("ColorToColorTable called with nil hexStr, returning default color {0, 0, 0, 0}")
|
||||
return { 0, 0, 0, 0 }
|
||||
end
|
||||
|
||||
@@ -266,11 +259,11 @@ function DrawingHelper.ColorToColorTable(hexStr)
|
||||
return { r, g , b , a }
|
||||
end
|
||||
|
||||
---@param rgba table
|
||||
---@param rgba table { r, g, b, a }
|
||||
---@return string
|
||||
function DrawingHelper.ColorTableToColorString(rgba)
|
||||
if rgba == nil or #rgba < 4 or rgba[1] == nil or rgba[2] == nil or rgba[3] == nil or rgba[4] == nil then
|
||||
drawingLogger:warn("ColorTableToColorString called with invalid rgba table, returning default color string '0x00000000'")
|
||||
logger:warn("ColorTableToColorString called with invalid rgba table, returning default color string '0x00000000'")
|
||||
return "0x00000000"
|
||||
end
|
||||
|
||||
@@ -293,11 +286,11 @@ function DrawingHelper.ToLineStyleInteger(lineStyle)
|
||||
return 2
|
||||
elseif lineStyle == "dotted" then
|
||||
return 3
|
||||
elseif lineStyle == "dot dash" then
|
||||
elseif lineStyle == "dotdash" then
|
||||
return 4
|
||||
elseif lineStyle == "long dash" then
|
||||
elseif lineStyle == "longdash" then
|
||||
return 5
|
||||
elseif lineStyle == "two dash" then
|
||||
elseif lineStyle == "twodash" then
|
||||
return 6
|
||||
else
|
||||
return 0
|
||||
@@ -314,11 +307,11 @@ function DrawingHelper.ToLineStyleString(lineStyle)
|
||||
elseif lineStyle == 3 then
|
||||
return "dotted"
|
||||
elseif lineStyle == 4 then
|
||||
return "dot dash"
|
||||
return "dotdash"
|
||||
elseif lineStyle == 5 then
|
||||
return "long dash"
|
||||
return "longdash"
|
||||
elseif lineStyle == 6 then
|
||||
return "two dash"
|
||||
return "twodash"
|
||||
else
|
||||
return "no line"
|
||||
end
|
||||
|
||||
@@ -18,7 +18,7 @@ local RunwayStrikeMission = {}
|
||||
---@field corners Array<Vec2>
|
||||
---@field kilosHit number
|
||||
---@field repairGroups Array<string>
|
||||
---@field drawID number?
|
||||
---@field drawID Array<number>
|
||||
|
||||
|
||||
---@param runway Runway
|
||||
@@ -172,8 +172,10 @@ function RunwayStrikeMission:Draw()
|
||||
|
||||
runwaySection.drawID = DrawingHelper.Draw(drawObject)
|
||||
else
|
||||
DcsUtil.SetFillColor(runwaySection.drawID, fillColor)
|
||||
DcsUtil.SetLineColor(runwaySection.drawID, lineColor)
|
||||
for _, drawID in pairs(runwaySection.drawID) do
|
||||
DcsUtil.SetFillColor(drawID, fillColor)
|
||||
DcsUtil.SetLineColor(drawID, lineColor)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -497,6 +499,7 @@ function RunwayStrikeMission:ToSections(runway, numSections)
|
||||
corners = corners,
|
||||
kilosHit = 0,
|
||||
repairGroups = {},
|
||||
drawID = {},
|
||||
}
|
||||
|
||||
table.insert(sections, section)
|
||||
|
||||
@@ -126,6 +126,11 @@ function Mission:NotifyMissionComplete()
|
||||
|
||||
end
|
||||
|
||||
function Mission:ForceMissionComplete()
|
||||
self._state = "COMPLETED"
|
||||
self:NotifyMissionComplete()
|
||||
end
|
||||
|
||||
function Mission:MarkMissionAreaToGroup(groupId) end
|
||||
|
||||
---endregion
|
||||
|
||||
@@ -632,6 +632,12 @@ do -- INIT DCS_UTIL
|
||||
---@field b number
|
||||
---@field a number
|
||||
|
||||
local drawID = 4210
|
||||
|
||||
function DCS_UTIL.GetNextDrawID()
|
||||
drawID = drawID + 1
|
||||
return drawID
|
||||
end
|
||||
|
||||
---@param groupID number
|
||||
---@param text string
|
||||
@@ -677,6 +683,10 @@ do -- INIT DCS_UTIL
|
||||
---@param drawID number
|
||||
---@param fillColor DrawColor
|
||||
function DCS_UTIL.SetFillColor(drawID, fillColor)
|
||||
if fillColor == nil then
|
||||
return
|
||||
end
|
||||
|
||||
local lineColorMapped = {
|
||||
fillColor.r or 0,
|
||||
fillColor.g or 0,
|
||||
|
||||
@@ -128,6 +128,14 @@ do -- INIT UTIL
|
||||
return str:find('^' .. findable) ~= nil
|
||||
end
|
||||
|
||||
UTIL.endsWith = function(str, findable, ignoreCase)
|
||||
if ignoreCase == true then
|
||||
return string.lower(str):find(string.lower(findable) .. '$') ~= nil
|
||||
end
|
||||
|
||||
return str:find(findable .. '$') ~= nil
|
||||
end
|
||||
|
||||
---comment
|
||||
---@param str string
|
||||
---@param findable string
|
||||
|
||||
Reference in New Issue
Block a user