From 27548d69bbf8a25760cd72d8c4ce676bbd6236ad Mon Sep 17 00:00:00 2001 From: dutchie031 Date: Tue, 25 Aug 2026 18:40:07 +0200 Subject: [PATCH] Created lane feature that should be backwards compatible with non-lane features --- lanes.excalidraw.json | 445 ++++++++++++-- src/classes/api/SpearheadApi.lua | 5 +- src/classes/api/SpearheadApiDoc.lua | 3 +- src/classes/spearhead_events.lua | 21 +- .../stageClasses/GlobalStageManager.lua | 547 ++++++++++++------ src/classes/stageClasses/StageLane.lua | 178 ++++++ src/classes/stageClasses/StageRepository.lua | 47 ++ .../stageClasses/Stages/BaseStage/Stage.lua | 33 +- .../stageClasses/Stages/ExtraStage.lua | 2 +- .../stageClasses/Stages/PrimaryStage.lua | 2 +- .../stageClasses/Stages/WaitingStage.lua | 14 +- src/classes/util/Util.lua | 8 + src/main.lua | 4 +- 13 files changed, 1055 insertions(+), 254 deletions(-) create mode 100644 src/classes/stageClasses/StageLane.lua create mode 100644 src/classes/stageClasses/StageRepository.lua diff --git a/lanes.excalidraw.json b/lanes.excalidraw.json index 29e3464..b9aca9c 100644 --- a/lanes.excalidraw.json +++ b/lanes.excalidraw.json @@ -406,8 +406,8 @@ "type": 3 }, "seed": 1626473245, - "version": 468, - "versionNonce": 1827077469, + "version": 469, + "versionNonce": 874232424, "isDeleted": false, "boundElements": [ { @@ -417,13 +417,9 @@ { "id": "kIqbCgtXKr5-2qhRSYTAa", "type": "arrow" - }, - { - "id": "0CY7kgHkgaXEMGTFJiv2H", - "type": "arrow" } ], - "updated": 1787318071712, + "updated": 1787406477137, "link": null, "locked": false }, @@ -633,11 +629,11 @@ "type": 2 }, "seed": 2125327475, - "version": 36, - "versionNonce": 1265229288, + "version": 40, + "versionNonce": 1013087493, "isDeleted": true, "boundElements": [], - "updated": 1787403270962, + "updated": 1787675736572, "link": null, "locked": false, "points": [ @@ -773,8 +769,8 @@ "type": 3 }, "seed": 1931784787, - "version": 595, - "versionNonce": 648660125, + "version": 597, + "versionNonce": 196807877, "isDeleted": false, "boundElements": [ { @@ -786,15 +782,15 @@ "type": "arrow" }, { - "id": "0CY7kgHkgaXEMGTFJiv2H", + "id": "Q8b54GQSVn5ZBrXMwYjN7", "type": "arrow" }, { - "id": "Q8b54GQSVn5ZBrXMwYjN7", + "id": "MwTn5dJVKPmeTHOM-q3f9", "type": "arrow" } ], - "updated": 1787318161674, + "updated": 1787675772937, "link": null, "locked": false }, @@ -1488,11 +1484,11 @@ "type": 2 }, "seed": 536727613, - "version": 139, - "versionNonce": 1741218173, - "isDeleted": false, + "version": 140, + "versionNonce": 399876888, + "isDeleted": true, "boundElements": [], - "updated": 1787318074593, + "updated": 1787406477136, "link": null, "locked": false, "points": [ @@ -1772,8 +1768,8 @@ "type": 3 }, "seed": 1971420499, - "version": 285, - "versionNonce": 598897235, + "version": 286, + "versionNonce": 142740965, "isDeleted": false, "boundElements": [ { @@ -1783,9 +1779,13 @@ { "id": "w6_Fv5GP_VEFqSQMXy25N", "type": "arrow" + }, + { + "id": "o3WprJN3wZEDcHw1DiFYr", + "type": "arrow" } ], - "updated": 1787318114912, + "updated": 1787675785325, "link": null, "locked": false }, @@ -1889,7 +1889,7 @@ "id": "qO3tWw8uDrdY-GSL5hbG7", "type": "rectangle", "x": 1611.9167066751852, - "y": 1250.2170926328931, + "y": 1206.2170926328931, "width": 144, "height": 99, "angle": 0, @@ -1907,8 +1907,8 @@ "type": 3 }, "seed": 2008474461, - "version": 330, - "versionNonce": 2001596957, + "version": 502, + "versionNonce": 72829003, "isDeleted": false, "boundElements": [ { @@ -1918,9 +1918,13 @@ { "id": "Q8b54GQSVn5ZBrXMwYjN7", "type": "arrow" + }, + { + "id": "Z4Fy52Fl_c9lXHJlrhd9g", + "type": "arrow" } ], - "updated": 1787318163603, + "updated": 1787675800613, "link": null, "locked": false }, @@ -1928,7 +1932,7 @@ "id": "EFDaczSk3yi8gn2b8hoI_", "type": "text", "x": 1632.626736277236, - "y": 1287.2170926328931, + "y": 1243.2170926328931, "width": 102.57994079589844, "height": 25, "angle": 0, @@ -1944,11 +1948,11 @@ "index": "ag", "roundness": null, "seed": 958164925, - "version": 298, - "versionNonce": 374795869, + "version": 467, + "versionNonce": 328458027, "isDeleted": false, "boundElements": [], - "updated": 1787318158139, + "updated": 1787675761637, "link": null, "locked": false, "text": "STAGE C5", @@ -1967,7 +1971,7 @@ "x": 1449.9192687785321, "y": 980.5000000000001, "width": 155.99743789665308, - "height": 315.1857377213224, + "height": 273.41807104570137, "angle": 0, "strokeColor": "#ffffff", "backgroundColor": "#1971c2", @@ -1983,11 +1987,11 @@ "type": 2 }, "seed": 935284605, - "version": 184, - "versionNonce": 822381789, + "version": 353, + "versionNonce": 667206091, "isDeleted": false, "boundElements": [], - "updated": 1787318165497, + "updated": 1787675761637, "link": null, "locked": false, "points": [ @@ -2001,7 +2005,7 @@ ], [ 155.99743789665308, - 315.1857377213224 + 273.41807104570137 ] ], "startBinding": { @@ -2022,6 +2026,377 @@ }, "startArrowhead": null, "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "oIXVKbkqP10kd7HsX3Ma8", + "type": "rectangle", + "x": 1845.5, + "y": 878, + "width": 144, + "height": 99, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#e8590c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "ai", + "roundness": { + "type": 3 + }, + "seed": 1637710309, + "version": 783, + "versionNonce": 1692360869, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "7AVtQifmKK8XdbWOLnM1M" + }, + { + "id": "MwTn5dJVKPmeTHOM-q3f9", + "type": "arrow" + }, + { + "id": "o3WprJN3wZEDcHw1DiFYr", + "type": "arrow" + }, + { + "id": "Z4Fy52Fl_c9lXHJlrhd9g", + "type": "arrow" + } + ], + "updated": 1787675805417, + "link": null, + "locked": false + }, + { + "id": "7AVtQifmKK8XdbWOLnM1M", + "type": "text", + "x": 1872.2800216674805, + "y": 915, + "width": 90.43995666503906, + "height": 25, + "angle": 0, + "strokeColor": "#ffffff", + "backgroundColor": "#96f2d7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aj", + "roundness": null, + "seed": 1576990021, + "version": 739, + "versionNonce": 761448939, + "isDeleted": false, + "boundElements": [], + "updated": 1787675780472, + "link": null, + "locked": false, + "text": "STAGE 6", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "oIXVKbkqP10kd7HsX3Ma8", + "originalText": "STAGE 6", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "MwTn5dJVKPmeTHOM-q3f9", + "type": "arrow", + "x": 1497, + "y": 925.0522131170663, + "width": 342.5, + "height": 2.4153737658673435, + "angle": 0, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "ak", + "roundness": { + "type": 2 + }, + "seed": 1633560229, + "version": 99, + "versionNonce": 1709724811, + "isDeleted": false, + "boundElements": null, + "updated": 1787675780472, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 342.5, + 2.4153737658673435 + ] + ], + "startBinding": { + "elementId": "UoiunLPIaAwLLJTJGcSj8", + "mode": "orbit", + "fixedPoint": [ + 1, + 0.5001 + ] + }, + "endBinding": { + "elementId": "oIXVKbkqP10kd7HsX3Ma8", + "mode": "orbit", + "fixedPoint": [ + 0, + 0.5001 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "o3WprJN3wZEDcHw1DiFYr", + "type": "arrow", + "x": 1755.5, + "y": 765.5, + "width": 84.00000000000023, + "height": 141.54545454545473, + "angle": 0, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "al", + "roundness": { + "type": 2 + }, + "seed": 68654021, + "version": 179, + "versionNonce": 1367219755, + "isDeleted": false, + "boundElements": [], + "updated": 1787675789610, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 43, + 33 + ], + [ + 42, + 132 + ], + [ + 84.00000000000023, + 141.54545454545473 + ] + ], + "startBinding": { + "elementId": "Dug48Ksr7mdITJ1CSQdgJ", + "mode": "inside", + "fixedPoint": [ + 0.9971062036445457, + 0.47830856212545203 + ] + }, + "endBinding": { + "elementId": "oIXVKbkqP10kd7HsX3Ma8", + "mode": "orbit", + "fixedPoint": [ + 0.45884773662551637, + 0.45884773662551726 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "GPK9crZA0GMljhRqF5HDa", + "type": "text", + "x": 1794.2073421171517, + "y": 835.5194758205485, + "width": 8, + "height": 25, + "angle": 0, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "am", + "roundness": null, + "seed": 1055479941, + "version": 3, + "versionNonce": 1276059851, + "isDeleted": true, + "boundElements": null, + "updated": 1787675789610, + "link": null, + "locked": false, + "text": "", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "o3WprJN3wZEDcHw1DiFYr", + "originalText": "", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "v_3ygDgE5RD8LQhGEvhTX", + "type": "arrow", + "x": 1662.1960283823262, + "y": 1200.2170926328931, + "width": 128.30397161767382, + "height": 194.71709263289313, + "angle": 0, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "an", + "roundness": { + "type": 2 + }, + "seed": 1602252997, + "version": 261, + "versionNonce": 1283832069, + "isDeleted": true, + "boundElements": null, + "updated": 1787675799132, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 128.30397161767382, + -194.71709263289313 + ] + ], + "startBinding": { + "elementId": "qO3tWw8uDrdY-GSL5hbG7", + "mode": "orbit", + "fixedPoint": [ + 0.2207233413264886, + 0.22072334132648888 + ] + }, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false, + "fixedSegments": null, + "startIsSpecial": null, + "endIsSpecial": null + }, + { + "id": "Z4Fy52Fl_c9lXHJlrhd9g", + "type": "arrow", + "x": 1761.9167066751852, + "y": 1248.0538669619218, + "width": 78.55616398479242, + "height": 286.26754389891505, + "angle": 0, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "ao", + "roundness": { + "type": 2 + }, + "seed": 1569002603, + "version": 382, + "versionNonce": 2105101093, + "isDeleted": false, + "boundElements": null, + "updated": 1787675810843, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 28.583293324814804, + -36.55386696192181 + ], + [ + 4.583293324814804, + -269.5538669619218 + ], + [ + 78.55616398479242, + -286.26754389891505 + ] + ], + "startBinding": { + "elementId": "qO3tWw8uDrdY-GSL5hbG7", + "mode": "orbit", + "fixedPoint": [ + 1, + 0.5001 + ] + }, + "endBinding": { + "elementId": "oIXVKbkqP10kd7HsX3Ma8", + "mode": "orbit", + "fixedPoint": [ + 0.24597691208358835, + 0.7540230879164157 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", "elbowed": false } ], diff --git a/src/classes/api/SpearheadApi.lua b/src/classes/api/SpearheadApi.lua index 4cbc90b..58da472 100644 --- a/src/classes/api/SpearheadApi.lua +++ b/src/classes/api/SpearheadApi.lua @@ -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 diff --git a/src/classes/api/SpearheadApiDoc.lua b/src/classes/api/SpearheadApiDoc.lua index ac35da3..9344d7d 100644 --- a/src/classes/api/SpearheadApiDoc.lua +++ b/src/classes/api/SpearheadApiDoc.lua @@ -6,7 +6,7 @@ SpearheadAPI = SpearheadAPI ---@class SpearheadStagesAPI ---@field changeStage fun(stageNumber: number): boolean, string @Changes the active stage of spearhead.
All other stages will change based on the normal logic. (CAP, BLUE etc.) ---@field getCurrentStage fun(): number | nil @Returns the current stange number
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.
@param stageNumber number
@return boolean | nil
@return string +---@field isStageComplete fun(stageNumber: number, stageLaneIdentifier: string?): boolean | nil, string @returns whether a stage (by index) is complete.
@param stageNumber number
@param stageLaneIdentifier string? nil for default lane
@return boolean | nil
@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.
@param listener OnMissionCompleteListener
@return void - diff --git a/src/classes/spearhead_events.lua b/src/classes/spearhead_events.lua index f3f19fd..efc707b 100644 --- a/src/classes/spearhead_events.lua +++ b/src/classes/spearhead_events.lua @@ -29,7 +29,7 @@ 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 = {} @@ -37,17 +37,11 @@ do ---Add a stage zone number changed listener ---@param listener OnStageChangedListener object with function OnStageNumberChanged(self, number) SpearheadEvents.AddStageNumberChangedListener = function(listener) - 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?") + if type(listener) ~= "table" or type(listener.OnStageNumberChanged) ~= "function" then + warn("Event handler not of type table/object with function OnStageNumberChanged(self, number)") return end - table.insert(OnStageNumberChangedHandlers, handler) + table.insert(OnStageNumberChangedListeners, listener) end ---@class OnStageNumberChangeCompleteListener @@ -64,14 +58,15 @@ do end ---@param newStageNumber number - SpearheadEvents.PublishStageNumberChanged = function(newStageNumber) + ---@param laneIdentifier string? + SpearheadEvents.PublishStageNumberChanged = function(newStageNumber, laneIdentifier) pcall(function () Persistence.SetActiveStage(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 diff --git a/src/classes/stageClasses/GlobalStageManager.lua b/src/classes/stageClasses/GlobalStageManager.lua index b8b372c..744c5cb 100644 --- a/src/classes/stageClasses/GlobalStageManager.lua +++ b/src/classes/stageClasses/GlobalStageManager.lua @@ -8,17 +8,8 @@ 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> -local StagesByIndex = {} - ----@type table> -local SideStageByIndex = {} - ----@type table> -local WaitingStagesByIndex = {} +local StageRepository = require("classes.stageClasses.StageRepository") +local StageLane = require("classes.stageClasses.StageLane") local currentStage = -99 @@ -26,25 +17,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 +---@return GlobalStageManager function GlobalStageManager.NewAndStart(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.new() self.logger = logger if stageConfig.isAutoStages ~= true then @@ -55,161 +58,222 @@ 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 +---@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() == "InChapter" 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) self.logger:debug("Stage number changed to: " .. tostring(stageNumber)) @@ -252,67 +316,176 @@ function GlobalStageManager:UpdateDrawings(stageNumber) 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" + + -- 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 + -- 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 label = stage.stageName or stage.zoneName + + -- 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%d)"%s', + nodeId, nodeShape, label, stageIndex, 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 reaches a chapter start index for that side lane + 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 + local fromNodeId = mainLaneId .. "_" .. stageIdx + local toNodeId = sideId .. "_" .. stageIdx + -- Side lane activates when main lane reaches this chapter start + 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"} + for _, nodeStr in pairs(nodes) do + table.insert(diagramLines, nodeStr) + end + for _, edgeStr in pairs(edges) do + table.insert(diagramLines, edgeStr) + 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 diff --git a/src/classes/stageClasses/StageLane.lua b/src/classes/stageClasses/StageLane.lua new file mode 100644 index 0000000..c8a6441 --- /dev/null +++ b/src/classes/stageClasses/StageLane.lua @@ -0,0 +1,178 @@ + + +---@class StageLane +---@field private _isDefaultStageLane boolean +---@field private _stageLaneIdentifier string +---@field private _activeStageIndex number +---@field private _stagesInLaneByIndex table> +---@field private _chapterStarts table? +---@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.
+--- Chapter starts are the first StageIndex after a gap in stage numbers.
+--- 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.
+---@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 +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? +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 diff --git a/src/classes/stageClasses/StageRepository.lua b/src/classes/stageClasses/StageRepository.lua new file mode 100644 index 0000000..05ac679 --- /dev/null +++ b/src/classes/stageClasses/StageRepository.lua @@ -0,0 +1,47 @@ +local StageLane = require("classes.stageClasses.StageLane") + +---@class StageRepository +---@field private StageLanes table +local StageRepository = {} +StageRepository.__index = StageRepository + +---@return StageRepository +function StageRepository.new() + local self = setmetatable({}, StageRepository) + self.StageLanes = {} + self.StageLanes[tostring(StageLane.DefaultLaneKey)] = StageLane.New(StageLane.DefaultLaneKey) + return self +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 +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 \ No newline at end of file diff --git a/src/classes/stageClasses/Stages/BaseStage/Stage.lua b/src/classes/stageClasses/Stages/BaseStage/Stage.lua index 9ce9183..2eda558 100644 --- a/src/classes/stageClasses/Stages/BaseStage/Stage.lua +++ b/src/classes/stageClasses/Stages/BaseStage/Stage.lua @@ -19,6 +19,11 @@ local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelpe ---| "BLUE" ---| "GRAY" +---@alias StageType +---| "PrimaryStage" +---| "ExtraStage" +---| "WaitingStage" + --- @class StageData --- @field stageBriefing string? --- @field missionsByCode table @@ -33,9 +38,11 @@ local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelpe --- @field supplyHubs Array --- @class StageInitData +--- @field type StageType --- @field stageZoneName string --- @field stageNumber integer --- @field stageDisplayName string +--- @field stageLaneIdentifier string? --- @class StageCompleteListener @@ -45,6 +52,8 @@ 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 @@ -86,15 +95,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.zoneName = initData.stageZoneName self.stageNumber = initData.stageNumber + 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 @@ -298,6 +314,21 @@ function Stage:IsActive() return self._currentStageState == Stage.CurrentStageState.ACTIVE 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 + ---comment function Stage:CheckAndUpdateSelf() self._logger:debug("Checking on Stage: " .. self.zoneName) diff --git a/src/classes/stageClasses/Stages/ExtraStage.lua b/src/classes/stageClasses/Stages/ExtraStage.lua index 175dd9f..c43ffa8 100644 --- a/src/classes/stageClasses/Stages/ExtraStage.lua +++ b/src/classes/stageClasses/Stages/ExtraStage.lua @@ -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) diff --git a/src/classes/stageClasses/Stages/PrimaryStage.lua b/src/classes/stageClasses/Stages/PrimaryStage.lua index cde2148..79dc0bf 100644 --- a/src/classes/stageClasses/Stages/PrimaryStage.lua +++ b/src/classes/stageClasses/Stages/PrimaryStage.lua @@ -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 diff --git a/src/classes/stageClasses/Stages/WaitingStage.lua b/src/classes/stageClasses/Stages/WaitingStage.lua index 13a79a0..a803a8a 100644 --- a/src/classes/stageClasses/Stages/WaitingStage.lua +++ b/src/classes/stageClasses/Stages/WaitingStage.lua @@ -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) diff --git a/src/classes/util/Util.lua b/src/classes/util/Util.lua index f825790..f8366c4 100644 --- a/src/classes/util/Util.lua +++ b/src/classes/util/Util.lua @@ -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 diff --git a/src/main.lua b/src/main.lua index 4d361f1..b775078 100644 --- a/src/main.lua +++ b/src/main.lua @@ -54,7 +54,7 @@ local detectionLogger = Logger.new("DetectionManager", defaultLogLevel) local detectionManager = DetectionManager.New(detectionLogger) GlobalCapManager.start(databaseManager, capConfig, detectionManager, stageConfig, defaultLogLevel, spawnManager) -GlobalStageManager.NewAndStart(databaseManager, stageConfig, defaultLogLevel, spawnManager) +local globalStageManager = GlobalStageManager.NewAndStart(databaseManager, stageConfig, defaultLogLevel, spawnManager) GlobalFleetManager.start(databaseManager) local SetStageDelayed = function(number, time) @@ -70,5 +70,5 @@ standardLogger:info("Spearhead Initialisation duration: " .. tostring(duration) local missionEditorWarningsLogger = Logger.new("MissionEditorWarnings", defaultLogLevel) MissionEditorWarnings.WriteAll(missionEditorWarningsLogger) -GlobalStageManager:printFullOverview() +globalStageManager:PrintMermaidStage()