diff --git a/RELEASENOTES.md b/RELEASENOTES.md index effc01c..5e31f9c 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -5,11 +5,19 @@ ### New Features +- Issue + +- Issue: #35 + Creating Lane option for stages. + Additionally to the primary stage lane, stages can now be created in different lanes. + These lanes can be used to create different stage progression flows and create different "side-stories". + #40 + ### Bug Fixes ## [0.13.0] 2026-08 -A good first release that finally has all major bugs fixed that were caused by the migration from both the underlying script transpiler and the migration to Gitea.
+A good first release that finally has all major bugs fixed that were caused by the migration from both the underlying script transpiler and the migration to Gitea. ### Breaking Changes diff --git a/config.lua b/config.lua index f429a7f..14af0b5 100644 --- a/config.lua +++ b/config.lua @@ -4,6 +4,9 @@ SpearheadConfig = { ---DEBUG LOGGING debugEnabled = false, -- default false + --- DEBUG MENU + debugMenuEnabled = false, -- default false + --- The time briefings should be displayed by default. --- Players can always "Clear Messages" through the F10 menu, so setting it to a high value can be briefingMessageDuration = 60, --default 60 diff --git a/src/classes/capClasses/CapAirbase.lua b/src/classes/capClasses/CapAirbase.lua index a906f28..80390a3 100644 --- a/src/classes/capClasses/CapAirbase.lua +++ b/src/classes/capClasses/CapAirbase.lua @@ -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 diff --git a/src/classes/capClasses/airGroups/AirGroup.lua b/src/classes/capClasses/airGroups/AirGroup.lua index ea5a5ce..83c1586 100644 --- a/src/classes/capClasses/airGroups/AirGroup.lua +++ b/src/classes/capClasses/airGroups/AirGroup.lua @@ -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._routeDrawing = nil 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 diff --git a/src/classes/configuration/GlobalConfig.lua b/src/classes/configuration/GlobalConfig.lua index c794984..d4c19d8 100644 --- a/src/classes/configuration/GlobalConfig.lua +++ b/src/classes/configuration/GlobalConfig.lua @@ -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 diff --git a/src/classes/fleetClasses/FleetGroup.lua b/src/classes/fleetClasses/FleetGroup.lua index 2e703dc..48d7671 100644 --- a/src/classes/fleetClasses/FleetGroup.lua +++ b/src/classes/fleetClasses/FleetGroup.lua @@ -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] diff --git a/src/classes/persistence/Persistence.lua b/src/classes/persistence/Persistence.lua index 02e179e..7e3331d 100644 --- a/src/classes/persistence/Persistence.lua +++ b/src/classes/persistence/Persistence.lua @@ -13,7 +13,7 @@ do ---@field unitsStates table ---@field random_missions table ---@field deliveredKilos table - ---@field activeStage integer|nil + ---@field activeStageInStageLane table ---@class UnitState ---@field isDead boolean @@ -33,7 +33,7 @@ do unitsStates = {}, random_missions = {}, deliveredKilos = {}, - activeStage = nil + activeStageInStageLane = {} } local logger = {} @@ -230,9 +230,11 @@ 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 @@ -256,12 +258,11 @@ do end ---Get the active stage as in the persistance file + ---@param stageLane string? ---@return integer|nil - Persistence.GetActiveStage = function() - if tables.activeStage then - return tables.activeStage - end - return nil + Persistence.GetActiveStage = function(stageLane) + stageLane = stageLane or "nil" + return tables.activeStageInStageLane[stageLane] end ---comment diff --git a/src/classes/spearhead_events.lua b/src/classes/spearhead_events.lua index efc707b..6b05b14 100644 --- a/src/classes/spearhead_events.lua +++ b/src/classes/spearhead_events.lua @@ -45,13 +45,13 @@ do 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) @@ -61,7 +61,7 @@ do ---@param laneIdentifier string? SpearheadEvents.PublishStageNumberChanged = function(newStageNumber, laneIdentifier) pcall(function () - Persistence.SetActiveStage(newStageNumber) + Persistence.SetActiveStage(laneIdentifier, newStageNumber) end) for _, callable in pairs(OnStageNumberChangedListeners) do @@ -82,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) diff --git a/src/classes/stageClasses/GlobalStageManager.lua b/src/classes/stageClasses/GlobalStageManager.lua index 744c5cb..b93c768 100644 --- a/src/classes/stageClasses/GlobalStageManager.lua +++ b/src/classes/stageClasses/GlobalStageManager.lua @@ -3,13 +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 StageRepository = require("classes.stageClasses.StageRepository") local StageLane = require("classes.stageClasses.StageLane") +local Persistence = require("classes.persistence.Persistence") local currentStage = -99 @@ -33,7 +34,7 @@ local singletonInstance = nil ---@param logLevel LogLevel ---@param spawnManager SpawnManager ---@return GlobalStageManager -function GlobalStageManager.NewAndStart(database, stageConfig, logLevel, spawnManager) +function GlobalStageManager.new(database, stageConfig, logLevel, spawnManager) if singletonInstance ~= nil then return singletonInstance @@ -47,7 +48,7 @@ function GlobalStageManager.NewAndStart(database, stageConfig, logLevel, spawnMa self.database = database self.stageConfig = stageConfig self._missionCommandsHelper = MissionCommandsHelper.getOrCreate() - self._stageRepository = StageRepository.new() + self._stageRepository = StageRepository.getInstance() self.logger = logger if stageConfig.isAutoStages ~= true then @@ -92,6 +93,38 @@ function GlobalStageManager.NewAndStart(database, stageConfig, logLevel, spawnMa 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 @@ -204,10 +237,9 @@ function GlobalStageManager:OnStageComplete(stage) -- 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 + 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) @@ -275,14 +307,19 @@ function GlobalStageManager:IsStageComplete(stageNumber, stageLaneIdentifier) 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 = {} diff --git a/src/classes/stageClasses/StageRepository.lua b/src/classes/stageClasses/StageRepository.lua index 05ac679..31f8840 100644 --- a/src/classes/stageClasses/StageRepository.lua +++ b/src/classes/stageClasses/StageRepository.lua @@ -5,14 +5,27 @@ local StageLane = require("classes.stageClasses.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? diff --git a/src/classes/stageClasses/Stages/BaseStage/Stage.lua b/src/classes/stageClasses/Stages/BaseStage/Stage.lua index 2eda558..d3f41e2 100644 --- a/src/classes/stageClasses/Stages/BaseStage/Stage.lua +++ b/src/classes/stageClasses/Stages/BaseStage/Stage.lua @@ -329,6 +329,23 @@ 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 function Stage:CheckAndUpdateSelf() self._logger:debug("Checking on Stage: " .. self.zoneName) @@ -389,7 +406,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")) @@ -514,7 +531,12 @@ end ---comment ---@param self Stage ---@param number integer -function Stage:OnStageNumberChanged(number) +---@param stageLaneIdentifier string? +function Stage:OnStageNumberChanged(number, stageLaneIdentifier) + + if stageLaneIdentifier ~= self._stageLaneIdentifier then + return + end if self._activeStage == number then --only activate once for a stage return diff --git a/src/classes/stageClasses/Stages/ExtraStage.lua b/src/classes/stageClasses/Stages/ExtraStage.lua index c43ffa8..b4cd633 100644 --- a/src/classes/stageClasses/Stages/ExtraStage.lua +++ b/src/classes/stageClasses/Stages/ExtraStage.lua @@ -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 diff --git a/src/classes/stageClasses/drawings/CustomDrawing.lua b/src/classes/stageClasses/drawings/CustomDrawing.lua index 55fc646..71ba0a6 100644 --- a/src/classes/stageClasses/drawings/CustomDrawing.lua +++ b/src/classes/stageClasses/drawings/CustomDrawing.lua @@ -33,6 +33,36 @@ function CustomDrawing.New(drawingObject) return self end +---@param points Array +---@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 diff --git a/src/classes/stageClasses/drawings/helper/DrawingHelper.lua b/src/classes/stageClasses/drawings/helper/DrawingHelper.lua index 8432890..a3b7472 100644 --- a/src/classes/stageClasses/drawings/helper/DrawingHelper.lua +++ b/src/classes/stageClasses/drawings/helper/DrawingHelper.lua @@ -266,7 +266,7 @@ 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 diff --git a/src/classes/stageClasses/missions/baseMissions/Mission.lua b/src/classes/stageClasses/missions/baseMissions/Mission.lua index 54b1afc..f5272b7 100644 --- a/src/classes/stageClasses/missions/baseMissions/Mission.lua +++ b/src/classes/stageClasses/missions/baseMissions/Mission.lua @@ -126,6 +126,11 @@ function Mission:NotifyMissionComplete() end +function Mission:ForceMissionComplete() + self._state = "COMPLETED" + self:NotifyMissionComplete() +end + function Mission:MarkMissionAreaToGroup(groupId) end ---endregion diff --git a/src/main.lua b/src/main.lua index b775078..56e3f69 100644 --- a/src/main.lua +++ b/src/main.lua @@ -5,9 +5,11 @@ local DcsUtil = require("classes.util.DcsUtil") local Database = require("classes.spearhead_db") local SpearheadEvents = require("classes.spearhead_events") local MissionCommandsHelper = require("classes.stageClasses.helpers.MissionCommandsHelper") +local GlobalConfig = require("classes.configuration.GlobalConfig") local CapConfig = require("classes.configuration.CapConfig") local StageConfig = require("classes.configuration.StageConfig") local Persistence = require("classes.persistence.Persistence") +local PersistenceConfig = require("classes.configuration.PersistenceConfig") local SpawnManager = require("classes.helpers.SpawnManager") local DetectionManager = require("classes.capClasses.detection.DetectionManager") local GlobalCapManager = require("classes.capClasses.GlobalCapManager") @@ -33,19 +35,11 @@ MissionCommandsHelper.getOrCreate() -- initiate local capConfig = CapConfig:new(); local stageConfig = StageConfig:getInstance(); -local startingStage = stageConfig.startingStage or 1 -if SpearheadConfig and SpearheadConfig.Persistence and SpearheadConfig.Persistence.enabled == true then - standardLogger:info("Persistence enabled") +local persistenceConfig = PersistenceConfig.new() + +if persistenceConfig and persistenceConfig:isEnabled() == true then local persistenceLogger = Logger.new("Persistence", defaultLogLevel) Persistence.Init(persistenceLogger) - - local persistanceStage = Persistence.GetActiveStage() - if persistanceStage then - standardLogger:info("Persistance activated and using persistant active stage: " .. persistanceStage) - startingStage = persistanceStage - end -else - standardLogger:info("Persistence disabled") end local spawnLogger = Logger.new("SpawnManager", defaultLogLevel) @@ -54,16 +48,9 @@ local detectionLogger = Logger.new("DetectionManager", defaultLogLevel) local detectionManager = DetectionManager.New(detectionLogger) GlobalCapManager.start(databaseManager, capConfig, detectionManager, stageConfig, defaultLogLevel, spawnManager) -local globalStageManager = GlobalStageManager.NewAndStart(databaseManager, stageConfig, defaultLogLevel, spawnManager) +local globalStageManager = GlobalStageManager.new(databaseManager, stageConfig, defaultLogLevel, spawnManager) GlobalFleetManager.start(databaseManager) -local SetStageDelayed = function(number, time) - SpearheadEvents.PublishStageNumberChanged(number) - return nil -end - -timer.scheduleFunction(SetStageDelayed, startingStage, timer.getTime() + 3) - env.info(startTime .. "ms / " .. timer.getTime() * 1000 .. "ms") local duration = (timer.getTime() * 1000) - startTime standardLogger:info("Spearhead Initialisation duration: " .. tostring(duration) .. "ms") @@ -72,3 +59,17 @@ local missionEditorWarningsLogger = Logger.new("MissionEditorWarnings", defaultL MissionEditorWarnings.WriteAll(missionEditorWarningsLogger) globalStageManager:PrintMermaidStage() +local startDelayed = function() + globalStageManager:Start() + return nil +end + +timer.scheduleFunction(startDelayed, nil, timer.getTime() + 5) -- delay start by 5 seconds so every deleted item it first removed entirely + +local globalConfig = GlobalConfig.New() +if globalConfig and globalConfig:isDebugMenuEnabled() == true then + local DebugMenu = require("classes.debug.DebugMenu") + local debugMenu = DebugMenu.new() + debugMenu:RegisterMenus() +end +