This commit is contained in:
2026-08-30 14:12:43 +02:00
parent 27548d69bb
commit 51bed10822
16 changed files with 214 additions and 49 deletions
+9 -1
View File
@@ -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. <br>
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
+3
View File
@@ -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
+5 -2
View File
@@ -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._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
@@ -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
+3 -1
View File
@@ -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]
+10 -9
View File
@@ -13,7 +13,7 @@ do
---@field unitsStates table<string, UnitState>
---@field random_missions table<string, MissionState>
---@field deliveredKilos table<string, number>
---@field activeStage integer|nil
---@field activeStageInStageLane table<string, number>
---@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
+4 -4
View File
@@ -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)
@@ -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 = {}
@@ -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?
@@ -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
@@ -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
@@ -33,6 +33,36 @@ function CustomDrawing.New(drawingObject)
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
@@ -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
@@ -126,6 +126,11 @@ function Mission:NotifyMissionComplete()
end
function Mission:ForceMissionComplete()
self._state = "COMPLETED"
self:NotifyMissionComplete()
end
function Mission:MarkMissionAreaToGroup(groupId) end
---endregion
+20 -19
View File
@@ -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