Refactored Persistance managed by SpawnManager for groups, and added … (#53)

* Refactored Persistance managed by SpawnManager for groups, and added Persistence for buildables
* get last file based on the iteration number
* Working persistence of everything as a MVP version
This commit is contained in:
2025-07-11 20:06:09 +02:00
committed by GitHub
parent 3d19137970
commit b6a1285bde
30 changed files with 994 additions and 763 deletions
Binary file not shown.
+5 -4
View File
@@ -32,8 +32,9 @@ end
---@param stageConfig table
---@param runwayBombingTracker RunwayBombingTracker
---@param detectionManager DetectionManager
---@param spawnManager SpawnManager
---@return CapBase
function CapBase.new(airbaseName, database, logger, capConfig, stageConfig, runwayBombingTracker, detectionManager)
function CapBase.new(airbaseName, database, logger, capConfig, stageConfig, runwayBombingTracker, detectionManager, spawnManager)
CapBase.__index = CapBase
local self = setmetatable({}, { __index = CapBase }) --[[@as CapBase]]
@@ -53,7 +54,7 @@ function CapBase.new(airbaseName, database, logger, capConfig, stageConfig, runw
local baseData = database:getAirbaseDataForZone(airbaseName)
if baseData and baseData.CapGroups then
for key, name in pairs(baseData.CapGroups) do
local capGroup = Spearhead.classes.capClasses.airGroups.CapGroup.New(name, capConfig, logger)
local capGroup = Spearhead.classes.capClasses.airGroups.CapGroup.New(name, capConfig, logger, spawnManager)
if capGroup then
self.capGroupsByName[name] = capGroup
end
@@ -62,7 +63,7 @@ function CapBase.new(airbaseName, database, logger, capConfig, stageConfig, runw
if baseData and baseData.SweepGroups then
for key, name in pairs(baseData.SweepGroups) do
local sweepGroup = Spearhead.classes.capClasses.airGroups.SweepGroup.New(name, capConfig, logger)
local sweepGroup = Spearhead.classes.capClasses.airGroups.SweepGroup.New(name, capConfig, logger, spawnManager)
if sweepGroup then
self.sweepGroupsByName[name] = sweepGroup
end
@@ -71,7 +72,7 @@ function CapBase.new(airbaseName, database, logger, capConfig, stageConfig, runw
if baseData and baseData.InterceptGroups then
for key, name in pairs(baseData.InterceptGroups) do
local interceptGroup = Spearhead.classes.capClasses.airGroups.InterceptGroup.New(name, capConfig, logger, detectionManager)
local interceptGroup = Spearhead.classes.capClasses.airGroups.InterceptGroup.New(name, capConfig, logger, detectionManager, spawnManager)
if interceptGroup then
self.interceptGroupsByName[name] = interceptGroup
end
+5 -2
View File
@@ -13,7 +13,8 @@ do
---@param stageConfig StageConfig
---@param detectionManager DetectionManager
---@param logLevel LogLevel
function GlobalCapManager.start(database, capConfig, detectionManager, stageConfig, logLevel)
---@param spawnManager SpawnManager
function GlobalCapManager.start(database, capConfig, detectionManager, stageConfig, logLevel, spawnManager)
if initiated == true then return end
local logger = Spearhead.LoggerTemplate.new("AirbaseManager", logLevel)
@@ -32,7 +33,9 @@ do
for _, airbaseName in pairs(airbaseNames) do
if airbaseName then
local airbaseSpecificLogger = Spearhead.LoggerTemplate.new("CAP_" .. airbaseName, logLevel)
local airbase = Spearhead.classes.capClasses.CapAirbase.new(airbaseName, database, airbaseSpecificLogger, capConfig, stageConfig, runwayBombingTracker, detectionManager)
local airbase = Spearhead.classes.capClasses.CapAirbase.new(airbaseName, database, airbaseSpecificLogger, capConfig, stageConfig, runwayBombingTracker, detectionManager, spawnManager)
if airbase then
table.insert(airbasesPerStage[stageName], airbase)
allAirbasesByName[airbaseName] = airbase
+14 -4
View File
@@ -6,6 +6,7 @@
---@field protected _isSpawned boolean
---@field protected _config CapConfig
---@field protected _checkLivenessNumber number
---@field protected _spawnManager SpawnManager
local AirGroup = {}
AirGroup.__index = AirGroup
@@ -13,13 +14,15 @@ AirGroup.__index = AirGroup
---@param groupName string
---@param groupType AirGroupType
---@param config CapConfig
function AirGroup:New(groupName, groupType, config, logger)
---@param spawnManager SpawnManager
function AirGroup:New(groupName, groupType, config, logger, spawnManager)
self._groupName = groupName
self._groupType = groupType
self._isSpawned = false
self._state = "UnSpawned" -- Default state
self._config = config
self._logger = logger
self._spawnManager = spawnManager
local group = Group.getByName(self._groupName)
if group then
@@ -31,7 +34,8 @@ function AirGroup:New(groupName, groupType, config, logger)
Spearhead.Events.addOnUnitLostEventListener(unit:getName(), self)
Spearhead.Events.addOnUnitLandEventListener(unit:getName(), self)
end
Spearhead.DcsUtil.DestroyGroup(self._groupName)
spawnManager:DestroyGroup(groupName)
end
end
@@ -135,13 +139,19 @@ function AirGroup:SpawnInternal(force, withoutLoadout)
if self._isSpawned and force ~= true then return end
local group, isStatic = Spearhead.DcsUtil.SpawnGroupTemplate(self._groupName, nil, nil, true, nil, withoutLoadout)
---@type SpawnOverrides
local overrides = {
emptyLoadouts = withoutLoadout,
uncontrolled = true
}
local group, isStatic = self._spawnManager:SpawnGroup(self._groupName, overrides, false)
if isStatic == true then
--- If For Some reaons someone tries to schedule static units as CAP classes
self._state = "UnSpawned"
return
end
group = group --[[@as Group]]
if group then
self._group = group
self._isSpawned = true
+3 -2
View File
@@ -10,12 +10,13 @@ CapGroup.__index = CapGroup
---@param groupName string
---@param config CapConfig
---@param logger Logger
---@param spawnManager SpawnManager
---@return CapGroup
function CapGroup.New(groupName, config, logger)
function CapGroup.New(groupName, config, logger, spawnManager)
setmetatable(CapGroup, Spearhead.classes.capClasses.airGroups.AirGroup)
local self = setmetatable({}, CapGroup) --[[@as CapGroup]]
Spearhead.classes.capClasses.airGroups.AirGroup.New(self, groupName, "CAP", config, logger)
Spearhead.classes.capClasses.airGroups.AirGroup.New(self, groupName, "CAP", config, logger, spawnManager)
self._targetZoneIdPerStage = {}
@@ -18,12 +18,13 @@ InterceptGroup.__index = InterceptGroup
---@param config CapConfig
---@param logger Logger
---@param detectionManager DetectionManager
---@param spawnManager SpawnManager
---@return InterceptGroup?
function InterceptGroup.New(groupName, config, logger, detectionManager)
function InterceptGroup.New(groupName, config, logger, detectionManager, spawnManager)
setmetatable(InterceptGroup, Spearhead.classes.capClasses.airGroups.AirGroup)
local self = setmetatable({}, InterceptGroup)
Spearhead.classes.capClasses.airGroups.AirGroup.New(self, groupName, "INTERCEPT", config, logger)
Spearhead.classes.capClasses.airGroups.AirGroup.New(self, groupName, "INTERCEPT", config, logger, spawnManager)
local group = Group.getByName(groupName)
if not group then
+3 -2
View File
@@ -8,11 +8,12 @@ SweepGroup.__index = SweepGroup
---@param groupName string
---@param config CapConfig
---@param logger Logger
---@param spawnManager SpawnManager
---@return SweepGroup
function SweepGroup.New(groupName, config, logger)
function SweepGroup.New(groupName, config, logger, spawnManager)
setmetatable(SweepGroup, Spearhead.classes.capClasses.airGroups.AirGroup)
local self = setmetatable({}, SweepGroup) --[[@as SweepGroup]]
Spearhead.classes.capClasses.airGroups.AirGroup.New(self, groupName, "SWEEP", config, logger)
Spearhead.classes.capClasses.airGroups.AirGroup.New(self, groupName, "SWEEP", config, logger, spawnManager)
self._targetZoneIdPerStage = {}
self:InitWithName(groupName)
+1 -1
View File
@@ -8,7 +8,7 @@ GlobalFleetManager.start = function(database)
local logger = Spearhead.LoggerTemplate.new("CARRIERFLEET", "INFO")
local all_groups = Spearhead.DcsUtil.getAllGroupNames()
local all_groups = Spearhead.classes.helpers.MizGroupsManager.getAllGroupNames()
for _, groupName in pairs(all_groups) do
if Spearhead.Util.startswith(string.lower(groupName), "carriergroup" ) == true then
logger:info("Registering " .. groupName .. " as a managed fleet")
+81
View File
@@ -0,0 +1,81 @@
---@class SpawnData
---@field groupTemplate table
---@field isStatic boolean
---@field category number
---@field country number
---@class MizGroupsManager
---@field private _groupNames Array<string>
---@field private _spawnTemplateData table<string, SpawnData>
local MizGroupsManager = {}
MizGroupsManager._groupNames = {}
MizGroupsManager._spawnTemplateData = {}
do --init
for coalition_name, coalition_data in pairs(env.mission.coalition) do
local coalition_nr = Spearhead.DcsUtil.stringToCoalition(coalition_name)
if coalition_data.country then
for country_index, country_data in pairs(coalition_data.country) do
for category_name, categorydata in pairs(country_data) do
local category_id = Spearhead.DcsUtil.stringToGroupCategory(category_name)
if category_id ~= nil and type(categorydata) == "table" and categorydata.group ~= nil and type(categorydata.group) == "table" then
for group_index, group in pairs(categorydata.group) do
local name = group.name
local skippable = false
local isStatic = false
if category_id == Spearhead.DcsUtil.GroupCategory.STATIC then
isStatic = true
local unit = group.units[1]
if unit and unit.category == "Heliports" then
skippable = true
elseif unit and unit.name then
name = unit.name
else
env.error("Group " .. name .. " has no units, skipping it.")
skippable = true
end
end
if skippable == false then
MizGroupsManager._spawnTemplateData[name] =
{
isStatic = isStatic,
country = country_data.id,
category = category_id,
groupTemplate = group
}
table.insert(MizGroupsManager._groupNames, name)
end
end
end
end
end
end
end
end
---@return Array<string>
function MizGroupsManager.getAllGroupNames()
return MizGroupsManager._groupNames
end
---@return boolean?
---@param groupName string
function MizGroupsManager.IsGroupStatic(groupName)
local spawnData = MizGroupsManager._spawnTemplateData[groupName]
if spawnData then
return spawnData.isStatic
end
return nil
end
---@return SpawnData?
function MizGroupsManager.getSpawnTemplateData(groupName)
return MizGroupsManager._spawnTemplateData[groupName]
end
if not Spearhead then Spearhead = {} end
if not Spearhead.classes then Spearhead.classes = {} end
if not Spearhead.classes.helpers then Spearhead.classes.helpers = {} end
Spearhead.classes.helpers.MizGroupsManager = MizGroupsManager
+348
View File
@@ -0,0 +1,348 @@
---@class SpawnManager : OnUnitLostListener
---@field private _persistedUnits table<string,boolean>
---@field private _logger Logger
local SpawnManager = {}
SpawnManager.__index = SpawnManager
---@class SpawnOverrides
---@field countryID number?
---@field emptyLoadouts boolean? if true, the group will spawn with empty loadouts
---@field route table? route of the group. If nil will be the default route.
---@field uncontrolled boolean? Sets the group to be uncontrolled on spawn
---@param logger Logger
function SpawnManager.new(logger)
local self = setmetatable({}, SpawnManager)
self._logger = logger
self._persistedUnits = {} -- Stores units that should be persisted by name
return self
end
---comment
---@param groupName string
---@param overrides SpawnOverrides? overrides for the spawn
---@param isGroupPersistant boolean? whether or not a group and all its units should be persisted.
---@return StaticObject|Group|nil spawned
---@return boolean isStatic
function SpawnManager:SpawnGroup(groupName, overrides, isGroupPersistant)
local spawnData = Spearhead.classes.helpers.MizGroupsManager.getSpawnTemplateData(groupName)
if spawnData == nil then
env.error("SpawnManager:SpawnGroup - No spawn template found for group: " .. groupName)
return nil, false
end
if spawnData.isStatic == true then
return self:SpawnStaticInternal(groupName, spawnData, overrides, isGroupPersistant), true
else
return self:SpawnGroupInternal(groupName, spawnData, overrides, isGroupPersistant), false
end
end
function SpawnManager:DestroyGroup(groupName)
if groupName == nil then return end
if self:IsGroupStatic(groupName) == true then
local object = StaticObject.getByName(groupName)
if object ~= nil then
object:destroy()
else
env.error("SpawnManager:DestroyGroup - Static object not found: " .. groupName)
end
else
local group = Group.getByName(groupName)
if group and group:isExist() then
group:destroy()
else
env.error("SpawnManager:DestroyGroup - Group not found or does not exist: " .. groupName)
end
end
end
---comment
---@param groupName string
---@return boolean
function SpawnManager:IsGroupStatic(groupName)
local isStatic = Spearhead.classes.helpers.MizGroupsManager.IsGroupStatic(groupName)
if isStatic ~= nil then return isStatic end
return StaticObject.getByName(groupName) ~= nil
end
---@param object Object
function SpawnManager:OnUnitLost(object)
local name = object:getName()
if self._persistedUnits[name] then
local heading = 0
local pos = object:getPosition()
if pos then
heading = math.atan2(pos.x.z, pos.x.x)
if heading < 0 then
heading = heading + 2*math.pi
end
heading = heading
end
Spearhead.classes.persistence.Persistence.UnitKilled(
name,
object:getPoint(),
heading,
object:getTypeName()
)
end
end
function SpawnManager:SpawnCorpsesOnly(groupName)
end
do --- privates
---@private
---@param groupName string
---@param spawnData SpawnData
---@param override SpawnOverrides?
---@param isPersistent boolean?
---@return Group|nil
function SpawnManager:SpawnGroupInternal(groupName, spawnData, override, isPersistent)
if not spawnData then return end
local country = spawnData.country
if override then
country = override.countryID or spawnData.country
end
local spawnTemplate = Spearhead.Util.deepCopyTable(spawnData.groupTemplate)
---@type Array<string>
local removeableUnitNames = {}
--[[
TODO: Spawn units at "current"/LastKnown position with them going to the next waypoint.
ONLY when perstable data is found.
]]
if spawnTemplate and spawnTemplate["units"] then
for _, unit in pairs(spawnTemplate["units"]) do
local name = unit["name"]
Spearhead.Events.addOnUnitLostEventListener(name, self)
local state = Spearhead.classes.persistence.Persistence.UnitState(name)
if state then
if state.isDead == true then
removeableUnitNames[#removeableUnitNames+1] = name
end
end
if override and override.emptyLoadouts == true then
if unit["payload"] and unit["payload"]["pylons"] then
unit["payload"]["pylons"] = {}
end
end
if unit["parking"] then
unit["parking_landing"] = unit["parking"]
end
if unit["parking_id"] then
unit["parking_landing_id"] = unit["parking_id"]
end
end
if override and override.route ~= nil then
spawnTemplate["route"] = override.route
end
if override and override.uncontrolled ~= nil then
spawnTemplate["uncontrolled"] = override.uncontrolled
end
local group = coalition.addGroup(country, spawnData.category, spawnTemplate)
for _, unit in pairs(group:getUnits()) do
self:CheckUnitAndReplaceIfPersistentDead(unit)
if isPersistent == true then
self._persistedUnits[unit:getName()] = true
end
Spearhead.Events.addOnUnitLostEventListener(unit:getName(), self)
end
return group
end
return nil
end
---@private
---@param groupName string
---@param spawnData SpawnData
---@param isPersistent any
---@param overrides SpawnOverrides?
---@return StaticObject?
function SpawnManager:SpawnStaticInternal(groupName, spawnData, overrides, isPersistent)
if not spawnData then return end
local country = spawnData.country
if overrides then
country = overrides.countryID or spawnData.country
end
local spawnTemplate = Spearhead.Util.deepCopyTable(spawnData.groupTemplate)
--for static Objecst groupNames and unit names are the same and is always 1:1
local persistentState = Spearhead.classes.persistence.Persistence.UnitState(groupName)
if persistentState then
if persistentState.isDead == true then
spawnTemplate["dead"] = true
if persistentState.pos then
spawnTemplate["x"] = persistentState.pos.x
spawnTemplate["y"] = persistentState.pos.z
if spawnTemplate["units"] and spawnTemplate["units"][1] then
spawnTemplate["units"][1]["x"] = persistentState.pos.x
spawnTemplate["units"][1]["y"] = persistentState.pos.z
spawnTemplate["units"][1]["heading"] = persistentState.heading or 0
end
end
end
end
---disable diagnostic as -1 is actually valid
---@diagnostic disable-next-line: param-type-mismatch
coalition.addGroup(country, -1, spawnTemplate)
local object = StaticObject.getByName(groupName)
if object == nil then
env.error("Could not retrieve spawned static object after spawning with name: " .. groupName)
return nil
end
if isPersistent == true then
self._persistedUnits[groupName] = true
Spearhead.Events.addOnUnitLostEventListener(groupName, self)
end
return object
end
---@private
---@param unit Unit
function SpawnManager:CheckUnitAndReplaceIfPersistentDead(unit)
if not unit then return end
local deadState = Spearhead.classes.persistence.Persistence.UnitState(unit:getName())
if deadState and deadState.isDead == true then
unit:destroy() -- Destroy the unit if it is dead
local staticObject = {
["heading"] = deadState.heading or 0,
["type"] = deadState.type or unit:getTypeName(),
["name"] = unit:getName() .. "_dead",
["x"] = deadState.pos.x,
["y"] = deadState.pos.z,
["dead"] = true,
}
coalition.addStaticObject(unit:getCountry(), staticObject)
end
end
end
if not Spearhead then Spearhead = {} end
if not Spearhead.classes then Spearhead.classes = {} end
if not Spearhead.classes.helpers then Spearhead.classes.helpers = {} end
Spearhead.classes.helpers.SpawnManager = SpawnManager
--Old Spawn Method
-- --- spawns the units as specified in the mission file itself
-- --- location and route can be nil and will then use default route
-- ---@param groupName string
-- ---@param location table? vector 3 data. { x , z, alt }
-- ---@param route table? route of the group. If nil wil be the default route.
-- ---@param uncontrolled boolean? Sets the group to be uncontrolled on spawn
-- ---@param countryId CountryID? Overwrites the country
-- ---@param emptyLoadouts boolean? If true, the group will spawn with empty loadouts
-- ---@return table? new_group the Group class that was spawned
-- ---@return boolean? isStatic whether the group is a static or not
-- function DCS_UTIL.SpawnGroupTemplate(groupName, location, route, uncontrolled, countryId, emptyLoadouts)
-- if groupName == nil then
-- return nil, nil
-- end
-- local template = DCS_UTIL.GetMizGroupOrDefault(groupName, nil)
-- if template == nil then
-- return nil, nil
-- end
-- if template.category == DCS_UTIL.GroupCategory.STATIC then
-- --TODO: Implement location and route stuff
-- local spawn_template = template.group_template
-- local country = countryId or template.country_id
-- ---disable diagnostic as -1 is actually valid
-- ---@diagnostic disable-next-line: param-type-mismatch
-- coalition.addGroup(country, -1, spawn_template)
-- local object = StaticObject.getByName(spawn_template.name)
-- if object then
-- return object, true
-- end
-- else
-- local spawn_template = Spearhead.Util.deepCopyTable(template.group_template)
-- if location ~= nil then
-- local x_offset
-- if location.x ~= nil then x_offset = spawn_template.x - location.x end
-- local y_offset
-- if location.z ~= nil then y_offset = spawn_template.y - location.z end
-- spawn_template.x = location.x
-- spawn_template.y = location.z
-- for i, unit in pairs(spawn_template.units) do
-- unit.x = unit.x - x_offset
-- unit.y = unit.y - y_offset
-- unit.alt = location.alt
-- end
-- end
-- if spawn_template.units then
-- for _, unit in pairs(spawn_template.units) do
-- if unit["parking"] then
-- unit["parking_landing"] = unit["parking"]
-- end
-- if unit["parking_id"] then
-- unit["parking_landing_id"] = unit["parking_id"]
-- end
-- end
-- if emptyLoadouts == true then
-- for _, unit in pairs(spawn_template.units) do
-- if unit["payload"] and unit["payload"]["pylons"] then
-- unit["payload"]["pylons"] = {}
-- end
-- end
-- end
-- end
-- if route ~= nil then
-- spawn_template.route = route
-- end
-- if uncontrolled ~= nil then
-- spawn_template.uncontrolled = uncontrolled
-- end
-- local country = countryId or template.country_id
-- local new_group = coalition.addGroup(country, template.category, spawn_template)
-- return new_group, false
-- end
-- end
+164 -58
View File
@@ -1,43 +1,54 @@
---@class Perstistence
---@field private _path string
---@field private _updateRequired boolean
local Persistence = {}
do
local EXTENSION = "spearhead"
---@class PersistentData
---@field dead_units table<string, DeathState>
---@field version string
---@field unitsStates table<string, UnitState>
---@field random_missions table<string, MissionState>
---@field deliveredKilos table<string, number>
---@field activeStage integer|nil
---@class DeathState
---@class UnitState
---@field isDead boolean
---@field pos Vec3
---@field heading number
---@field type string
---@field country_id integer
---@field pos Vec3?
---@field heading number?
---@field type string?
local persistanceWriteIntervalSeconds = 15
local enabled = false
local version = "1.0.0"
---@type PersistentData
local tables = {
dead_units = {},
version = version,
unitsStates = {},
random_missions = {},
deliveredKilos = {},
activeStage = nil
}
local logger = {}
if SpearheadConfig == nil then SpearheadConfig = {} end
if SpearheadConfig.Persistence == nil then SpearheadConfig.Persistence = {} end
local path = nil
local updateRequired = false
local createFileIfNotExists = function()
if not path then return end
local createFileIfNotExists = function(path)
if not path or path == "" then
logger:error("Persistence file path is not set, cannot create file")
return
end
local f = io.open(path, "r")
if f == nil then
logger:info("Persistence file does not exist, creating new one at: " .. path)
f = io.open(path, "w+")
if f == nil then
logger:error("Could not create a file")
@@ -45,53 +56,42 @@ do
f:write("{}")
f:close()
end
logger:info("Created new persistence file at: " .. path)
else
f:close()
end
end
local loadTablesFromFile = function()
---@param path string
local loadTablesFromFile = function(path)
if not path then return end
logger:info("Loading data from persistance file...")
local f = io.open(path, "r")
if f == nil then
logger:error("Could not open persistence file for reading: " .. path)
return
end
local json = f:read("*a")
f:close()
logger:debug("Loaded persistence file content: " .. json)
local lua = net.json2lua(json) --[[@as PersistentData]]
if lua.activeStage then
logger:info("Found active stage from save: " .. lua.activeStage)
tables.activeStage = lua.activeStage
if lua then
tables = lua
logger:debug("Loaded persistence data: " .. Spearhead.Util.toString(lua))
else
logger:error("Could not load persistence file, using default tables")
end
if lua.dead_units then
logger:debug("Found saved dead units")
for name, deadState in pairs(lua.dead_units) do
logger:debug("Found saved dead unit: " .. name)
if type(deadState) == "table" then
tables.dead_units[name] = {
isDead = deadState.isDead == true,
pos = deadState.pos,
heading = deadState.heading,
type = deadState.type,
country_id = deadState.country_id
}
end
end
end
if tables.version == nil then tables.version = version end
end
local writeToFile = function()
if not path then return end
if not Persistence._path then return end
local path = Persistence._path
local f = io.open(path, "w+")
if f == nil then
error("Could not open file for writing")
@@ -104,11 +104,13 @@ do
if f ~= nil then
f:close()
end
Persistence._updateRequired = false
logger:info("Wrote persistence data to file")
end
local UpdateContinuous = function(null, time)
if updateRequired then
env.info("[Spearhead][Persistence] Checking up on persistence state...")
if Persistence._updateRequired == true then
local status, result = pcall(writeToFile)
if status == false then
env.error("[Spearhead][Persistence] Could not write state to file: " .. result)
@@ -129,33 +131,107 @@ do
end
local warnForNonPersistenceContinous = function(null, time)
trigger.action.outText("Persistence was enabeld, however, io and lfs are not available and no persistence will be done. Make sure to either disable persistence or fix the issues before continuing.", 10)
return time + 9
end
---@param dir string @BaseDirectory
---@return string
local getLastFileOrDefault = function(dir, startsWith, default)
local latestFile, lastNumber = default, 0
for file in lfs.dir(dir) do
local split = Spearhead.Util.split_string(file, ".")
local doesStartWith = Spearhead.Util.startswith(file, startsWith, true)
if split and #split > 0 and split[#split] == EXTENSION and doesStartWith == true then
local numberString = split[#split-1]
local number = tonumber(numberString)
if number ~= nil and number > lastNumber then
lastNumber = number
latestFile = file
end
end
end
return latestFile
end
---comment
---@param persistenceLogger Logger
Persistence.Init = function(persistenceLogger)
logger = persistenceLogger
logger:info("Initiating Persistence Manager")
logger:debug("Initiating Persistence Manager")
if lfs == nil or io == nil then
logger:error("lfs and io seem to be sanitized. Persistence is skipped and disabled")
enabled = false
timer.scheduleFunction(warnForNonPersistenceContinous, nil, timer.getTime() + 10)
return
end
path = (SpearheadConfig.Persistence.directory or (lfs.writedir() .. "\\Data" )) .. "\\" .. (SpearheadConfig.Persistence.fileName or "Spearhead_Persistence.json")
---@type string
local dir = lfs.writedir() .. "\\Data"
local fileName = "Spearhead_Persistence.0.spearhead"
if SpearheadConfig and SpearheadConfig.Persistence then
if SpearheadConfig.Persistence.fileName then
logger:info("Persistence file path: " .. path)
local userFileName = SpearheadConfig.Persistence.fileName
local split = Spearhead.Util.split_string(userFileName, ".")
createFileIfNotExists()
loadTablesFromFile()
if not split and #split < 3 then
split[#split+1] = "0"
split[#split+1] = "spearhead"
end
if tonumber(split[#split-1]) == nil then
split[#split+1] = "0"
split[#split+1] = "spearhead"
end
fileName = table.concat(split, ".")
end
if SpearheadConfig.Persistence.directory ~= nil then
dir = SpearheadConfig.Persistence.directory
end
end
local split = Spearhead.Util.split_string(fileName, ".")
local matchingPart = table.concat(Spearhead.Util.sublist(split, 1, #split-2), ".")
local lastFile = getLastFileOrDefault(dir--[[@as string]], matchingPart, fileName)
local fileSplit = Spearhead.Util.split_string(lastFile, ".")
fileSplit[#fileSplit-1] = tostring(tonumber(fileSplit[#fileSplit-1]) + 1)
fileName = table.concat(fileSplit, ".")
if lastFile ~= fileName then
logger:info("Found last persistence file: " .. lastFile)
else
logger:info("No previous persistence file found, using default: " .. fileName)
end
logger:info("New Persistence file name: " .. tostring(fileName))
local lastPath = dir .. "\\" .. lastFile
local path = dir .. "\\" .. fileName
Persistence._path = path
createFileIfNotExists(path)
loadTablesFromFile(lastPath)
timer.scheduleFunction(UpdateContinuous, nil, timer.getTime() + 120)
enabled = true
Persistence.UpdateNow()
end
---Sets the stage in the persistence table
---@param stageNumber number
Persistence.SetActiveStage = function(stageNumber)
tables.activeStage = stageNumber
updateRequired = true
Persistence._updateRequired = true
end
---comment
@@ -166,7 +242,7 @@ do
if tables.random_missions == nil then tables.random_missions = {} end
tables.random_missions[string.lower(missionName)] = pickedZone
updateRequired = true
Persistence._updateRequired = true
end
---Get the picked random mission from the persistence file
@@ -186,19 +262,49 @@ do
return nil
end
---Check if the unit was dead during the last save. Nil if persitance not enabled
---comment
---@param zoneName string
---@param kilos number
Persistence.SetZoneDeliveredKilos = function(zoneName, kilos)
if enabled == false then return end
if tables.deliveredKilos == nil then
tables.deliveredKilos = {}
end
tables.deliveredKilos[zoneName] = kilos
Persistence._updateRequired = true
end
Persistence.GetZoneDeliveredKilos = function(zoneName)
if enabled == false then return 0 end
if tables.deliveredKilos == nil then
tables.deliveredKilos = {}
end
return tables.deliveredKilos[zoneName] or 0
end
---Check if the unit was dead during the last save. Nil if persitance not enabled or no state is found
---@param unitName string name
---@return DeathState|nil { isDead, pos = {x,y,z}, heading, type, country_id }
Persistence.UnitDeadState = function(unitName)
---@return UnitState|nil { isDead, pos = {x,y,z}, heading, type, country_id }
Persistence.UnitState = function(unitName)
if Persistence.isEnabled() == false then
return nil
end
local entry = tables.dead_units[unitName]
local entry = tables.unitsStates[unitName]
if entry then
return entry
else
return { isDead = false }
---@type UnitState
local state = {
isDead = false
}
return state
end
end
@@ -207,19 +313,19 @@ do
---@param position Vec3 { x, y ,z }
---@param heading number
---@param type string
---@param country_id number
Persistence.UnitKilled = function (name, position, heading, type, country_id)
Persistence.UnitKilled = function (name, position, heading, type)
if enabled == false then return end
tables.dead_units[name] = {
isDead = true,
pos = position,
heading = heading,
type = type,
country_id = country_id,
logger:debug("Unit killed: " .. name .. ".. => persistenting")
tables.unitsStates[name] = {
isDead = true,
pos = position,
heading = heading,
type = type,
isCleaned = false
}
updateRequired = true
Persistence._updateRequired = true
end
end
+14 -227
View File
@@ -67,6 +67,18 @@ do -- INIT UTIL
return list[random]
end
---@param list Array
---@param start number start
---@param n number length
---@return Array
function UTIL.sublist(list, start, n)
local result = {}
for i = start, n do
result[#result + 1] = list[i]
end
return result
end
---@param str string
---@param find string
---@param replace string
@@ -504,17 +516,6 @@ do -- INIT DCS_UTIL
}
]] --
---@class MizGroupData
---@field category GroupCategory
---@field country_id integer
---@field group_template table
---@type table<string, MizGroupData>
DCS_UTIL.__miz_groups = {}
DCS_UTIL.__groupNames = {}
DCS_UTIL.__blueGroupNames = {}
DCS_UTIL.__redGroupNames = {}
--[[
zone = {
name,
@@ -574,54 +575,6 @@ do -- INIT DCS_UTIL
DCS_UTIL.__warehouseStartingCoalition = {}
function DCS_UTIL.__INIT()
do -- INITS ALL TABLES WITH DATA THAT's from the MIZ environment
do -- init group tables
for coalition_name, coalition_data in pairs(env.mission.coalition) do
local coalition_nr = DCS_UTIL.stringToCoalition(coalition_name)
if coalition_data.country then
for country_index, country_data in pairs(coalition_data.country) do
for category_name, categorydata in pairs(country_data) do
local category_id = DCS_UTIL.stringToGroupCategory(category_name)
if category_id ~= nil and type(categorydata) == "table" and categorydata.group ~= nil and type(categorydata.group) == "table" then
for group_index, group in pairs(categorydata.group) do
local name = group.name
local skippable = false
if category_id == DCS_UTIL.GroupCategory.STATIC then
local unit = group.units[1]
if unit and unit.category == "Heliports" then
skippable = true
elseif unit then
name = unit.name
else
env.error("Group " .. name .. " has no units, skipping it.")
skippable = true
end
end
if skippable == false then
table.insert(DCS_UTIL.__groupNames, name)
DCS_UTIL.__miz_groups[name] =
{
category = category_id,
country_id = country_data.id,
group_template = group
}
if coalition_nr == 1 then
table.insert(DCS_UTIL.__redGroupNames, name)
elseif coalition_nr == 2 then
table.insert(DCS_UTIL.__blueGroupNames, name)
end
end
end
end
end
end
end
end
end
do --init trigger zones
for i, trigger_zone in pairs(env.mission.triggers.zones) do
@@ -750,31 +703,6 @@ do -- INIT DCS_UTIL
return -1
end
---checks if the groupname is a static group
---@param groupName string
function DCS_UTIL.IsGroupStatic(groupName)
if DCS_UTIL.__miz_groups[groupName] then
return DCS_UTIL.__miz_groups[groupName].category == DCS_UTIL.GroupCategory.STATIC
end
return StaticObject.getByName(groupName) ~= nil
end
---destroy the given group
---@param groupName string
function DCS_UTIL.DestroyGroup(groupName)
if DCS_UTIL.IsGroupStatic(groupName) then
local object = StaticObject.getByName(groupName)
if object ~= nil then
object:destroy()
end
else
local group = Group.getByName(groupName)
if group and group:isExist() then
group:destroy()
end
end
end
---destroy the given unit
---@param unitName string
@@ -1049,43 +977,7 @@ do -- INIT DCS_UTIL
end
end
end
--- get the group config as per start of the mission
--- group = {
--- category,
--- country_id,
--- group_template
--- }
---@param groupname string groupName you're looking for
function DCS_UTIL.GetMizGroupOrDefault(groupname, default)
local group = DCS_UTIL.__miz_groups[groupname]
if group == nil then
return default
end
return group
end
---comment Get all group names. Can be a LOT
---Includes statics
---@return table groups
function DCS_UTIL.getAllGroupNames()
return DCS_UTIL.__groupNames
end
---comment Get all BLUE group names. Can be a LOT
---Includes statics
---@return table groups
function DCS_UTIL.getAllBlueGroupNames()
return DCS_UTIL.__blueGroupNames
end
---comment Get all RED group names. Can be a LOT
---Includes statics
---@return table groups
function DCS_UTIL.getAllRedGroupNames()
return DCS_UTIL.__redGroupNames
end
---comment Get all units that are players
---@return Array<Unit> units
function DCS_UTIL.getAllPlayerUnits()
@@ -1134,27 +1026,6 @@ do -- INIT DCS_UTIL
return result
end
---Spawn an corpse
---@param countryId number countryId
---@param unitType string
---@param location table { z, y, z}
---@param heading number
function DCS_UTIL.SpawnCorpse(countryId, unitName, unitType, location, heading)
local name = "dead_" .. unitName
local staticObj = {
["heading"] = heading,
--["shape_name"] = "stolovaya",
["type"] = unitType,
["name"] = name,
["y"] = location.z,
["x"] = location.x,
["dead"] = true,
}
coalition.addStaticObject(countryId, staticObj)
end
function DCS_UTIL.CleanCorpse(unitName)
local unitName = "dead_" .. unitName
@@ -1296,90 +1167,6 @@ do -- INIT DCS_UTIL
end
end
--- spawns the units as specified in the mission file itself
--- location and route can be nil and will then use default route
---@param groupName string
---@param location table? vector 3 data. { x , z, alt }
---@param route table? route of the group. If nil wil be the default route.
---@param uncontrolled boolean? Sets the group to be uncontrolled on spawn
---@param countryId CountryID? Overwrites the country
---@param emptyLoadouts boolean? If true, the group will spawn with empty loadouts
---@return table? new_group the Group class that was spawned
---@return boolean? isStatic whether the group is a static or not
function DCS_UTIL.SpawnGroupTemplate(groupName, location, route, uncontrolled, countryId, emptyLoadouts)
if groupName == nil then
return nil, nil
end
local template = DCS_UTIL.GetMizGroupOrDefault(groupName, nil)
if template == nil then
return nil, nil
end
if template.category == DCS_UTIL.GroupCategory.STATIC then
--TODO: Implement location and route stuff
local spawn_template = template.group_template
local country = countryId or template.country_id
---disable diagnostic as -1 is actually valid
---@diagnostic disable-next-line: param-type-mismatch
coalition.addGroup(country, -1, spawn_template)
local object = StaticObject.getByName(spawn_template.name)
if object then
return object, true
end
else
local spawn_template = Spearhead.Util.deepCopyTable(template.group_template)
if location ~= nil then
local x_offset
if location.x ~= nil then x_offset = spawn_template.x - location.x end
local y_offset
if location.z ~= nil then y_offset = spawn_template.y - location.z end
spawn_template.x = location.x
spawn_template.y = location.z
for i, unit in pairs(spawn_template.units) do
unit.x = unit.x - x_offset
unit.y = unit.y - y_offset
unit.alt = location.alt
end
end
if spawn_template.units then
for _, unit in pairs(spawn_template.units) do
if unit["parking"] then
unit["parking_landing"] = unit["parking"]
end
if unit["parking_id"] then
unit["parking_landing_id"] = unit["parking_id"]
end
end
if emptyLoadouts == true then
for _, unit in pairs(spawn_template.units) do
if unit["payload"] and unit["payload"]["pylons"] then
unit["payload"]["pylons"] = {}
end
end
end
end
if route ~= nil then
spawn_template.route = route
end
if uncontrolled ~= nil then
spawn_template.uncontrolled = uncontrolled
end
local country = countryId or template.country_id
local new_group = coalition.addGroup(country, template.category, spawn_template)
return new_group, false
end
end
---@return number? id
function DCS_UTIL.GetNeutralCountry()
@@ -1393,7 +1180,7 @@ do -- INIT DCS_UTIL
function DCS_UTIL.NeedsRTBInTen(groupName, fuelOffset)
local isBingo = Spearhead.DcsUtil.IsBingoFuel(groupName, fuelOffset)
local isBingo = DCS_UTIL.IsBingoFuel(groupName, fuelOffset)
if isBingo then return true end
local aliveUnits = 0
+6 -6
View File
@@ -470,7 +470,7 @@ end
---@private
function Database:initAvailableUnits()
do
local all_groups = Spearhead.DcsUtil.getAllGroupNames()
local all_groups = Spearhead.classes.helpers.MizGroupsManager.getAllGroupNames()
for _, value in pairs(all_groups) do
is_group_taken[value] = false
end
@@ -637,7 +637,7 @@ end
---@private
function Database:loadBlueSamUnits()
local all_groups = Spearhead.DcsUtil.getAllGroupNames()
local all_groups = Spearhead.classes.helpers.MizGroupsManager.getAllGroupNames()
for _, blueSamZone in pairs(self._tables.BlueSams) do
local samData = self:getOrCreateBlueSamDataForZone(blueSamZone)
local groups = Spearhead.DcsUtil.getGroupsInZone(all_groups, blueSamZone)
@@ -659,7 +659,7 @@ function Database:loadMissionzoneUnits()
local groups = Spearhead.DcsUtil.getGroupsInZone(all_groups, missionZoneName)
for _, groupName in pairs(groups) do
if Spearhead.DcsUtil.IsGroupStatic(groupName) == true then
if Spearhead.classes.helpers.MizGroupsManager.IsGroupStatic(groupName) == true then
local object = StaticObject.getByName(groupName)
if object and object:getCoalition() == coalition.side.RED then
@@ -691,7 +691,7 @@ function Database:loadRandomMissionzoneUnits()
}
local groups = Spearhead.DcsUtil.getGroupsInZone(all_groups, missionZoneName)
for _, groupName in pairs(groups) do
if Spearhead.DcsUtil.IsGroupStatic(groupName) == true then
if Spearhead.classes.helpers.MizGroupsManager.IsGroupStatic(groupName) == true then
local object = StaticObject.getByName(groupName)
if object and object:getCoalition() == coalition.side.RED then
@@ -752,7 +752,7 @@ function Database:loadAirbaseGroups()
if airbaseZone and base:getDesc().category == Airbase.Category.AIRDROME then
local groups = Spearhead.DcsUtil.areGroupsInCustomZone(all_groups, airbaseZone)
for _, groupName in pairs(groups) do
if Spearhead.DcsUtil.IsGroupStatic(groupName) == true then
if Spearhead.classes.helpers.MizGroupsManager.IsGroupStatic(groupName) == true then
local object = StaticObject.getByName(groupName)
if object then
if object:getCoalition() == coalition.side.RED then
@@ -790,7 +790,7 @@ function Database:loadMiscGroupsInStages()
local groups = Spearhead.DcsUtil.getGroupsInZone(all_groups, stageZone.StageZoneName)
for _, groupName in pairs(groups) do
if Spearhead.DcsUtil.IsGroupStatic(groupName) == true then
if Spearhead.classes.helpers.MizGroupsManager.IsGroupStatic(groupName) == true then
local object = StaticObject.getByName(groupName)
if object and object:getCoalition() ~= coalition.side.NEUTRAL then
is_group_taken[groupName] = true
+4 -4
View File
@@ -355,17 +355,17 @@ do
local AI_GROUPS = {}
local function CheckAndTriggerSpawnAsync(unit, time)
local function isPlayer(unit)
if unit == nil then return false, "unit is nil" end
if unit.getGroup == nil then return false, 'no get group function in unit object, most likely static' end
if Object.getCategory(unit) ~= Object.Category.UNIT then
return false, "object is not a unit"
end
if unit:isExist() ~= true then return false, "unit does not exist" end
local group = unit:getGroup()
if group ~= nil then
if Spearhead.DcsUtil.IsGroupStatic(group:getName()) == true then
return false
end
if AI_GROUPS[group:getName()] == true then
return false
end
+5 -4
View File
@@ -20,8 +20,9 @@ GlobalStageManager = {}
---@param database Database
---@param stageConfig StageConfig
---@param logLevel LogLevel
---@param spawnManager SpawnManager
---@return nil
function GlobalStageManager:NewAndStart(database, stageConfig, logLevel)
function GlobalStageManager:NewAndStart(database, stageConfig, logLevel, spawnManager)
local logger = Spearhead.LoggerTemplate.new("StageManager", logLevel)
logger:info("Using Stage Log Level: " .. logLevel)
local o = {}
@@ -134,13 +135,13 @@ function GlobalStageManager:NewAndStart(database, stageConfig, logLevel)
}
if isSideStage == true then
local stage = Spearhead.classes.stageClasses.Stages.ExtraStage.New(database, stageConfig, stagelogger, initData)
local stage = Spearhead.classes.stageClasses.Stages.ExtraStage.New(database, stageConfig, stagelogger, initData, spawnManager)
stage:AddStageCompleteListener(OnStageCompleteListener)
if SideStageByIndex[tostring(orderNumber)] == nil then SideStageByIndex[tostring(orderNumber)] = {} end
table.insert(SideStageByIndex[tostring(orderNumber)], stage)
else
local stage = Spearhead.classes.stageClasses.Stages.PrimaryStage.New(database, stageConfig, stagelogger, initData)
local stage = Spearhead.classes.stageClasses.Stages.PrimaryStage.New(database, stageConfig, stagelogger, initData, spawnManager)
stage:AddStageCompleteListener(OnStageCompleteListener)
if StagesByIndex[tostring(orderNumber)] == nil then StagesByIndex[tostring(orderNumber)] = {} end
@@ -185,7 +186,7 @@ function GlobalStageManager:NewAndStart(database, stageConfig, logLevel)
stageZoneName = stageName,
waitingSeconds = waitingSeconds --[[@as integer]]
}
local waitingStage = Spearhead.classes.stageClasses.Stages.WaitingStage.New(database, stageConfig, stagelogger, initData)
local waitingStage = Spearhead.classes.stageClasses.Stages.WaitingStage.New(database, stageConfig, stagelogger, initData, spawnManager)
if WaitingStagesByIndex[tostring(stageIndex)] == nil then
WaitingStagesByIndex[tostring(stageIndex)] = {}
+45 -85
View File
@@ -2,22 +2,38 @@
---@class SpearheadGroup : OnUnitLostListener
---@field groupName string
---@field private _groupName string
---@field private _isStatic boolean
---@field private _isSpawned boolean
---@field private _spawnManager SpawnManager
---@field private _isPersistent boolean
local SpearheadGroup = {}
SpearheadGroup.__index = SpearheadGroup
function SpearheadGroup.New(groupName)
---comment
---@param groupName string
---@param spawnManager SpawnManager
---@param isPersistent boolean?
---@return SpearheadGroup
function SpearheadGroup.New(groupName, spawnManager, isPersistent)
local self = setmetatable({}, SpearheadGroup)
self._isStatic = Spearhead.DcsUtil.IsGroupStatic(groupName) == true
self.groupName = groupName
if isPersistent == nil then isPersistent = false end
self._spawnManager = spawnManager
self._isStatic = spawnManager:IsGroupStatic(groupName) == true
self._groupName = groupName
self._isSpawned = false
self._isPersistent = isPersistent
return self
end
function SpearheadGroup:GetName()
return self._groupName
end
function SpearheadGroup:IsSpawned()
return self._isSpawned
end
@@ -25,27 +41,8 @@ end
function SpearheadGroup:SpawnCorpsesOnly()
if self._isSpawned == true then return end
local group, isStatic = Spearhead.DcsUtil.SpawnGroupTemplate(self.groupName)
if isStatic == true then
self._isStatic = true
local deathState = Spearhead.classes.persistence.Persistence.UnitDeadState(self.groupName)
if deathState and deathState.isDead == true then
Spearhead.DcsUtil.DestroyGroup(self.groupName)
Spearhead.DcsUtil.SpawnCorpse(deathState.country_id, self.groupName, deathState.type, deathState.pos, deathState.heading)
end
else
if group then
for _, unit in pairs(group:getUnits()) do
local deathState = Spearhead.classes.persistence.Persistence.UnitDeadState(unit:getName())
Spearhead.DcsUtil.DestroyUnit(unit:getName())
if deathState and deathState.isDead == true then
Spearhead.DcsUtil.SpawnCorpse(deathState.country_id, unit:getName(), deathState.type, deathState.pos, deathState.heading)
end
end
end
end
self._spawnManager:SpawnCorpsesOnly(self._groupName)
self._isSpawned = true
end
@@ -55,52 +52,19 @@ function SpearheadGroup:Spawn(lateStart)
if self._isSpawned == true then return end
local spawned, isStatic = Spearhead.DcsUtil.SpawnGroupTemplate(self.groupName, nil, nil, lateStart)
if spawned and isStatic == false then
for _, unit in pairs(spawned:getUnits()) do
local deathState = Spearhead.classes.persistence.Persistence.UnitDeadState(unit:getName())
if deathState and deathState.isDead == true then
Spearhead.DcsUtil.DestroyUnit(self.groupName, unit:getName())
Spearhead.DcsUtil.SpawnCorpse(deathState.country_id, unit:getName(), deathState.type, deathState.pos, deathState.heading)
else
Spearhead.Events.addOnUnitLostEventListener(unit:getName(), self)
end
Spearhead.Events.addOnUnitLostEventListener(unit:getName(), self)
end
elseif spawned and isStatic == true then
if spawned then
Spearhead.Events.addOnUnitLostEventListener(spawned:getName(), self)
end
else
Spearhead.LoggerTemplate.new("SPEARHEADGROUP", "ERROR"):error("Failed to spawn group: " .. self.groupName)
end
---@type SpawnOverrides
local overrides = {
uncontrolled = lateStart or false,
}
local spawnedObject, isStatic = self._spawnManager:SpawnGroup(self._groupName, overrides, self._isPersistent)
self._isStatic = isStatic
self._isSpawned = true
end
function SpearheadGroup:Activate()
if self._isStatic == true then return end
local group = Group.getByName(self.groupName)
if group then
group:activate()
local controller = group:getController()
if controller then
controller:setCommand({
id = 'Start',
params = {}
})
end
end
end
function SpearheadGroup:Destroy()
self._isSpawned = false
Spearhead.DcsUtil.DestroyGroup(self.groupName)
self._spawnManager:DestroyGroup(self._groupName)
end
---@return boolean
@@ -112,13 +76,13 @@ end
---@return integer
function SpearheadGroup:GetCoalition()
if self._isStatic == true then
local object = StaticObject.getByName(self.groupName)
local object = StaticObject.getByName(self._groupName)
if object == nil then
return 0
end
return object:getCoalition()
else
local group = Group.getByName(self.groupName)
local group = Group.getByName(self._groupName)
if group == nil then
return 0
end
@@ -126,28 +90,18 @@ function SpearheadGroup:GetCoalition()
end
end
function SpearheadGroup:OnUnitLost(object)
local name = object:getName()
local pos = object:getPoint()
local type = object:getDesc().typeName
local position = object:getPosition()
local heading = math.atan2(position.x.z, position.x.x)
local country_id = object:getCountry()
Spearhead.classes.persistence.Persistence.UnitKilled(name, pos, heading, type, country_id)
end
---comment
---@return table result list of objects
function SpearheadGroup:GetObjects()
local result = {}
if self._isStatic == true then
local staticObject = StaticObject.getByName(self.groupName)
local staticObject = StaticObject.getByName(self._groupName)
if staticObject then
table.insert(result, staticObject)
end
else
local group = Group.getByName(self.groupName)
local group = Group.getByName(self._groupName)
if not group then return {} end
for _, unit in pairs(group:getUnits()) do
table.insert(result, unit)
@@ -165,7 +119,7 @@ function SpearheadGroup:GetAsUnits()
end
local result = {}
local group = Group.getByName(self.groupName)
local group = Group.getByName(self._groupName)
if not group then return {} end
for _, unit in pairs(group:getUnits()) do
table.insert(result, unit)
@@ -178,12 +132,12 @@ function SpearheadGroup:GetAllUnitPositions()
local result = {}
if self._isStatic == true then
local staticObject = StaticObject.getByName(self.groupName)
local staticObject = StaticObject.getByName(self._groupName)
if staticObject then
table.insert(result, staticObject:getPoint())
end
else
local group = Group.getByName(self.groupName)
local group = Group.getByName(self._groupName)
if not group then return {} end
for _, unit in pairs(group:getUnits()) do
table.insert(result, unit:getPoint())
@@ -196,9 +150,15 @@ function SpearheadGroup:SetInvisible()
if self._isStatic == true then
local country = Spearhead.DcsUtil.GetNeutralCountry()
Spearhead.DcsUtil.SpawnGroupTemplate(self.groupName, nil, nil, nil, country)
---@type SpawnOverrides
local overrides = {
countryID = country
}
self._spawnManager:SpawnGroup(self._groupName, overrides, self._isPersistent)
else
local group = Group.getByName(self.groupName)
local group = Group.getByName(self._groupName)
if group then
local setInvisible = {
id = 'SetInvisible',
@@ -215,7 +175,7 @@ end
function SpearheadGroup:SetVisible()
local group = Group.getByName(self.groupName)
local group = Group.getByName(self._groupName)
if group then
local setInvisible = {
id = 'SetInvisible',
+20 -43
View File
@@ -1,5 +1,5 @@
---@class BlueSam : OnCrateDroppedListener, MissionCompleteListener
---@class BlueSam : BuildableZone
---@field Activate fun(self: BlueSam)
---@field private _database Database
---@field private _logger Logger
@@ -11,13 +11,16 @@
---@field private _unitsPerCrate number?
---@field private _buildableMission BuildableMission?
local BlueSam = {}
BlueSam.__index = BlueSam
---@param database Database
---@param logger Logger
---@param zoneName string
---@param spawnManager SpawnManager
---@return BlueSam?
function BlueSam.New(database, logger, zoneName)
BlueSam.__index = BlueSam
function BlueSam.New(database, logger, zoneName, spawnManager)
setmetatable(BlueSam, Spearhead.classes.stageClasses.SpecialZones.abstract.BuildableZone)
local self = setmetatable({}, BlueSam)
self._database = database
@@ -44,15 +47,19 @@ function BlueSam.New(database, logger, zoneName)
local redUnitsPos = {}
local buildable = false
if self._buildableCrateKilos and self._buildableCrateKilos > 0 then
buildable = true
end
for _, groupName in pairs(blueSamData.groups) do
local SpearheadGroup = Spearhead.classes.stageClasses.Groups.SpearheadGroup.New(groupName)
local SpearheadGroup = Spearhead.classes.stageClasses.Groups.SpearheadGroup.New(groupName, spawnManager, true)
if SpearheadGroup then
if SpearheadGroup:GetCoalition() == 2 then
if SpearheadGroup:GetCoalition() == 2 or SpearheadGroup:GetCoalition() == 0 then
table.insert(self._blueGroups, SpearheadGroup)
end
for _, unit in pairs(SpearheadGroup:GetObjects()) do
if SpearheadGroup:GetCoalition() == 1 then
table.insert(blueUnitsPos, unit:getPoint())
@@ -76,21 +83,11 @@ function BlueSam.New(database, logger, zoneName)
end
end
if self._buildableCrateKilos ~= nil and self._buildableCrateKilos ~= 0 then
self._logger:debug("Buildable mission creating for zone: " .. zoneName .. " with crates: " .. self._buildableCrateKilos)
local noLandingZone = self:GetNoLandingZone()
local zone = Spearhead.DcsUtil.getZoneByName(zoneName)
if zone then
self._buildableMission = Spearhead.classes.stageClasses.missions.BuildableMission.new(database, logger, zone, noLandingZone, self._buildableCrateKilos, "SAM_CRATE")
self._buildableMission:AddOnCrateDroppedOfListener(self)
self._buildableMission:AddMissionCompleteListener(self)
end
local zone = Spearhead.DcsUtil.getZoneByName(zoneName)
if zone then
Spearhead.classes.stageClasses.SpecialZones.abstract.BuildableZone.New(self, zone, self._buildableCrateKilos or 0, "SAM_CRATE", self._blueGroups, logger, database)
end
return self
end
@@ -132,21 +129,14 @@ function BlueSam:Activate()
if self._buildableMission == nil then
self:SpawnGroups()
else
self._buildableMission:SpawnActive()
self:StartBuildable()
end
end
function BlueSam:SpawnGroups()
for unitName, needsCleanup in pairs(self._cleanupUnits) do
if needsCleanup == true then
Spearhead.DcsUtil.DestroyUnit(unitName)
else
local deathState = Spearhead.classes.persistence.Persistence.UnitDeadState(unitName)
if deathState and deathState.isDead == true then
Spearhead.DcsUtil.SpawnCorpse(deathState.country_id, unitName, deathState.type, deathState.pos, deathState.heading)
end
end
Spearhead.DcsUtil.DestroyUnit(unitName)
end
for _, group in pairs(self._blueGroups) do
@@ -154,25 +144,12 @@ function BlueSam:SpawnGroups()
end
end
---@param buildableMission BuildableMission
---@param kilos number
function BlueSam:OnCrateDroppedOff(buildableMission, kilos)
self._logger:debug("Crate dropped off in zone: " .. self._zoneName)
self._receivedKilos = self._receivedKilos + kilos
end
---@param buildableMission Mission
function BlueSam:OnMissionComplete(buildableMission)
self._logger:debug("Buildable mission complete for zone: " .. self._zoneName)
function BlueSam:OnBuildingComplete()
self:SpawnGroups()
end
if Spearhead == nil then Spearhead = {} end
if Spearhead.classes == nil then Spearhead.classes = {} end
if Spearhead.classes.stageClasses == nil then Spearhead.classes.stageClasses = {} end
+15 -157
View File
@@ -1,16 +1,12 @@
---@class FarpZone : OnCrateDroppedListener, MissionCompleteListener
---@class FarpZone: BuildableZone
---@field private _startingFarp boolean
---@field private _groups Array<SpearheadGroup>
---@field private _padNames Array<string>
---@field private _database Database
---@field private _logger Logger
---@field private _zoneName string
---@field private _requiredBuildingKilos number
---@field private _receivedBuildingKilos number
---@field private _groupsPerKilo number
---@field private _buildableMission BuildableMission?
---@field private _supplyHubs Array<SupplyHub>
local FarpZone = {}
FarpZone.__index = FarpZone
@@ -19,10 +15,10 @@ FarpZone.__index = FarpZone
---@param database Database
---@param logger Logger
---@param zoneName string
---@param spawnManager SpawnManager
---@return FarpZone
function FarpZone.New(database, logger, zoneName)
FarpZone.__index = FarpZone
function FarpZone.New(database, logger, zoneName, spawnManager)
setmetatable(FarpZone, Spearhead.classes.stageClasses.SpecialZones.abstract.BuildableZone)
local self = setmetatable({}, FarpZone)
self._database = database
@@ -54,34 +50,18 @@ function FarpZone.New(database, logger, zoneName)
end
end
for _, groupName in pairs(farpData.groups) do
local group = Spearhead.classes.stageClasses.Groups.SpearheadGroup.New(groupName)
local group = Spearhead.classes.stageClasses.Groups.SpearheadGroup.New(groupName, spawnManager, true)
table.insert(self._groups, group)
group:Destroy()
end
self._requiredBuildingKilos = farpData.buildingKilos
self._receivedBuildingKilos = 0
if self._requiredBuildingKilos ~= nil and self._requiredBuildingKilos > 0 then
self._logger:debug("FARP zone " .. zoneName .. " requires " .. self._requiredBuildingKilos .. " crates to be dropped off")
local noLandingZone = self:GetNoLandingZone()
local zone = Spearhead.DcsUtil.getZoneByName(zoneName)
if zone then
self._buildableMission = Spearhead.classes.stageClasses.missions.BuildableMission.new(database, logger, zone, noLandingZone, self._requiredBuildingKilos, "FARP_CRATE")
if self._buildableMission then
self._buildableMission:AddOnCrateDroppedOfListener(self)
self._buildableMission:AddMissionCompleteListener(self)
end
local totalGroups = Spearhead.Util.tableLength(self._groups)
self._groupsPerKilo = totalGroups / self._requiredBuildingKilos
end
local zone = Spearhead.DcsUtil.getZoneByName(zoneName)
if zone then
Spearhead.classes.stageClasses.SpecialZones.abstract.BuildableZone.New(self, zone, farpData.buildingKilos or 0, "FARP_CRATE", self._groups, logger, database)
end
end
if self._buildableMission == nil then
self._logger:debug("No buildable mission for zone: " .. zoneName)
end
self:Deactivate()
return self
@@ -101,7 +81,7 @@ function FarpZone:Activate()
self:SetPadsBlue()
self:ActivateSupplyHubs()
else
self._buildableMission:SpawnActive()
self:StartBuildable()
end
end
@@ -109,101 +89,13 @@ function FarpZone:Deactivate()
self:NeutralisePads()
end
---@class UnpackCrateParam
---@field self FarpZone
---@field kilos number
---@field groupsPerKilo number
---@field kilosPerSecond number
---@field unpackedItems number
---@field unpackedKilos number
---@param params UnpackCrateParam
---@param time number
local startUnpackingCrate = function(params, time)
local unpacked = params.unpackedKilos + (params.kilosPerSecond * 2)
local alreadySpawned = params.unpackedItems / params.groupsPerKilo
local diff = unpacked - alreadySpawned
local amount = math.floor(diff * params.groupsPerKilo)
local spawned = params.self:SpawnAmount(amount)
params.unpackedItems = params.unpackedItems + amount
params.unpackedKilos = unpacked
if params.unpackedKilos >= params.kilos or spawned == false then
params.self:FinaliseCrate(params.kilos)
return
end
return time + 2
end
---comment
---@param amount number
---@return boolean
function FarpZone:SpawnAmount(amount)
local function spawnOne()
for _, group in pairs(self._groups) do
if group:IsSpawned() == false then
group:Spawn()
return true
end
end
return nil
end
for i = 1, amount do
local spawned = spawnOne()
if spawned ~= true then
self._logger:debug("No more groups to spawn in zone: " .. self._zoneName)
return false
end
end
return true
end
---@param buildableMission BuildableMission
function FarpZone:OnCrateDroppedOff(buildableMission, kilos)
self._logger:debug("Crate dropped off in zone: " .. self._zoneName)
local timeToUnpack = (kilos / 500) * 15
---@type UnpackCrateParam
local params = {
self = self,
groupsPerKilo = self._groupsPerKilo,
unpackedItems = 0,
kilosPerSecond = kilos/timeToUnpack,
unpackedKilos = 0,
kilos = kilos
}
timer.scheduleFunction(startUnpackingCrate, params, timer.getTime() + 2)
end
---@param kilos number
function FarpZone:FinaliseCrate(kilos)
self._receivedBuildingKilos = self._receivedBuildingKilos + kilos
if self._receivedBuildingKilos >= self._requiredBuildingKilos then
self:BuildUp()
self:SetPadsBlue()
self:ActivateSupplyHubs()
end
end
---@param buildableMission Mission
function FarpZone:OnMissionComplete(buildableMission)
self._logger:debug("Buildable mission complete for zone: " .. self._zoneName)
-- self:BuildUp()
-- self:SetPadsBlue()
-- self:ActivateSupplyHubs()
function FarpZone:OnBuildingComplete()
self:BuildUp()
self:SetPadsBlue()
self:ActivateSupplyHubs()
end
---@private
function FarpZone:BuildUp()
for _, group in pairs(self._groups) do
group:Spawn()
@@ -239,40 +131,6 @@ function FarpZone:SetPadsBlue()
end
end
---@private
---@return SpearheadTriggerZone?
function FarpZone:GetNoLandingZone()
---@type Array<Vec2>
local points = {}
for _, group in pairs(self._groups) do
for _, unitPos in pairs(group:GetAllUnitPositions()) do
table.insert(points, { x = unitPos.x, y = unitPos.z })
end
end
local vecs = Spearhead.Util.getConvexHull(points)
local zone = Spearhead.DcsUtil.getZoneByName(self._zoneName)
if zone == nil then
self._logger:error("Zone not found: " .. self._zoneName)
return nil
end
---@type SpearheadTriggerZone
local spearheadZone = {
name = self._zoneName .. "_noland",
location = zone.location,
verts = vecs,
radius = 0,
zone_type = "Polygon"
}
return spearheadZone
end
if Spearhead == nil then Spearhead = {} end
if Spearhead.classes == nil then Spearhead.classes = {} end
if Spearhead.classes.stageClasses == nil then Spearhead.classes.stageClasses = {} end
+17 -137
View File
@@ -1,4 +1,4 @@
---@class StageBase : OnCrateDroppedListener, MissionCompleteListener
---@class StageBase : BuildableZone
---@field private _database Database
---@field private _logger Logger
---@field private _red_groups Array<SpearheadGroup>
@@ -12,14 +12,16 @@
---@field private _receivedBuildingKilos number
---@field private _buildableMission BuildableMission?
local StageBase = {}
StageBase.__index = StageBase
---comment
---@param databaseManager Database
---@param logger table
---@param airbaseName string
---@param spawnManager SpawnManager
---@return StageBase?
function StageBase.New(databaseManager, logger, airbaseName)
StageBase.__index = StageBase
function StageBase.New(databaseManager, logger, airbaseName, spawnManager)
setmetatable(StageBase, Spearhead.classes.stageClasses.SpecialZones.abstract.BuildableZone)
local self = setmetatable({}, StageBase)
self._database = databaseManager
@@ -47,7 +49,7 @@ function StageBase.New(databaseManager, logger, airbaseName)
local blueUnitsPos = {}
for _, groupName in pairs(airbaseData.RedGroups) do
local shGroup = Spearhead.classes.stageClasses.Groups.SpearheadGroup.New(groupName)
local shGroup = Spearhead.classes.stageClasses.Groups.SpearheadGroup.New(groupName, spawnManager, true)
table.insert(self._red_groups, shGroup)
for _, unit in pairs(shGroup:GetObjects()) do
@@ -58,7 +60,7 @@ function StageBase.New(databaseManager, logger, airbaseName)
end
for _, groupName in pairs(airbaseData.BlueGroups) do
local shGroup = Spearhead.classes.stageClasses.Groups.SpearheadGroup.New(groupName)
local shGroup = Spearhead.classes.stageClasses.Groups.SpearheadGroup.New(groupName, spawnManager, true)
table.insert(self._blue_groups, shGroup)
for _, unit in pairs(shGroup:GetObjects()) do
@@ -92,22 +94,9 @@ function StageBase.New(databaseManager, logger, airbaseName)
end
end
if airbaseData.buildingKilos ~= nil and airbaseData.buildingKilos > 0 then
self._logger:debug("Airbase " .. airbaseName .. " requires " .. tostring(airbaseData.buildingKilos) .. " kilos to be dropped off")
self._requiredBuildingKilos = airbaseData.buildingKilos
self._receivedBuildingKilos = 0
self._groupsPerKilo = Spearhead.Util.tableLength(self._blue_groups) / self._requiredBuildingKilos
local zone = Spearhead.DcsUtil.getAirbaseZoneByName(airbaseName)
if zone then
self._buildableMission = Spearhead.classes.stageClasses.missions.BuildableMission.new(databaseManager, logger, zone, self:GetNoLandingZone(), self._requiredBuildingKilos, "AIRBASE_CRATE")
end
if self._buildableMission then
self._buildableMission:AddOnCrateDroppedOfListener(self)
else
self._logger:error("Failed to create buildable mission for airbase: " .. airbaseName)
end
local zone = Spearhead.DcsUtil.getAirbaseZoneByName(airbaseName)
if zone then
Spearhead.classes.stageClasses.SpecialZones.abstract.BuildableZone.New(self, zone, airbaseData.buildingKilos or 0, "AIRBASE_CRATE", self._blue_groups, logger, databaseManager)
end
end
@@ -172,11 +161,12 @@ function StageBase:ActivateBlueStage()
self:CleanRedUnits()
if self._buildableMission and self._buildableMission:getState() ~= "COMPLETED" then
self._buildableMission:SpawnActive()
return
if self._buildableMission then
self:StartBuildable()
else
self:FinaliseBlueStage()
end
self:FinaliseBlueStage()
end
function StageBase:FinaliseBlueStage()
@@ -192,119 +182,9 @@ function StageBase:FinaliseBlueStage()
end
end
do ---Building parts
---@class UnpackAirbaseCrateParam
---@field self StageBase
---@field kilos number
---@field groupsPerKilo number
---@field kilosPerSecond number
---@field unpackedItems number
---@field unpackedKilos number
---@param params UnpackAirbaseCrateParam
---@param time number
local startUnpackingCrate = function(params, time)
local unpacked = params.unpackedKilos + (params.kilosPerSecond * 2)
local alreadySpawned = params.unpackedItems / params.groupsPerKilo
local diff = unpacked - alreadySpawned
local amount = math.floor(diff * params.groupsPerKilo)
local spawned = params.self:SpawnAmount(amount)
params.unpackedItems = params.unpackedItems + amount
params.unpackedKilos = unpacked
if params.unpackedKilos >= params.kilos or spawned == false then
params.self:FinaliseCrate(params.kilos)
return
end
return time + 2
end
---comment
---@param amount number
---@return boolean
function StageBase:SpawnAmount(amount)
local function spawnOne()
for _, group in pairs(self._blue_groups) do
if group:IsSpawned() == false then
group:Spawn()
return true
end
end
return nil
end
for i = 1, amount do
local spawned = spawnOne()
if spawned ~= true then
self._logger:debug("No more groups to spawn at base: " .. self._airbase:getName())
return false
end
end
return true
end
---@param buildableMission BuildableMission
function StageBase:OnCrateDroppedOff(buildableMission, kilos)
self._logger:debug("Crate dropped off at base: " .. self._airbase:getName())
local timeToUnpack = (kilos / 500) * 30
---@type UnpackAirbaseCrateParam
local params = {
self = self,
groupsPerKilo = self._groupsPerKilo,
unpackedItems = 0,
kilosPerSecond = kilos / timeToUnpack,
unpackedKilos = 0,
kilos = kilos
}
timer.scheduleFunction(startUnpackingCrate, params, timer.getTime() + 2)
end
---@private
---@return SpearheadTriggerZone?
function StageBase:GetNoLandingZone()
---@type Array<Vec2>
local points = {}
for _, group in pairs(self._blue_groups) do
for _, unitPos in pairs(group:GetAllUnitPositions()) do
table.insert(points, { x = unitPos.x, y = unitPos.z })
end
end
local vecs = Spearhead.Util.getConvexHull(points)
local pos = self._airbase:getPoint()
local location = {
x = pos.x,
y = pos.z
}
---@type SpearheadTriggerZone
local spearheadZone = {
name = self._airbase:getName() .. "_noland",
location = location,
verts = vecs,
radius = 0,
zone_type = "Polygon"
}
return spearheadZone
end
---@param kilos number
function StageBase:FinaliseCrate(kilos)
self._receivedBuildingKilos = self._receivedBuildingKilos + kilos
if self._receivedBuildingKilos >= self._requiredBuildingKilos then
self:FinaliseBlueStage()
end
end
function StageBase:OnBuildingComplete()
self:FinaliseBlueStage()
end
@@ -0,0 +1,204 @@
---@class BuildableZone : OnCrateDroppedListener
---@field protected _targetZone SpearheadTriggerZone
---@field protected _requiredKilos number
---@field protected _buildableMission BuildableMission?
---@field protected _buildableGroups Array<SpearheadGroup>
---@field private _groupsPerKilo number
---@field private _receivedBuildingKilos number
---@field private _buildableLogger Logger
---@field protected OnBuildingComplete fun(self:BuildableZone)
local BuildableZone = {}
BuildableZone.__index = BuildableZone
---@param targetZone SpearheadTriggerZone
---@param kilosRequired number
---@param buildableGroups Array<SpearheadGroup>
---@param database Database
---@param crateType SupplyType
---@param logger Logger
function BuildableZone:New(targetZone, kilosRequired, crateType, buildableGroups, logger, database)
self._targetZone = targetZone
self._requiredKilos = kilosRequired or 0
self._buildableGroups = buildableGroups or {}
self._buildableLogger = logger
local totalGroups = Spearhead.Util.tableLength(self._buildableGroups)
self._groupsPerKilo = totalGroups / self._requiredKilos
self._receivedBuildingKilos = 0
local persistedKilos = Spearhead.classes.persistence.Persistence.GetZoneDeliveredKilos(targetZone.name)
if persistedKilos and persistedKilos > 0 then
self._receivedBuildingKilos = persistedKilos
---@param params UnpackCrateParam
---@param time number
local startUnpackingCrate = function(params, time)
local unpacked = params.unpackedKilos + (params.kilosPerSecond * 2)
local alreadySpawned = params.unpackedItems / params.groupsPerKilo
local diff = unpacked - alreadySpawned
local amount = math.floor(diff * params.groupsPerKilo)
local spawned = params.self:SpawnAmount(amount)
params.unpackedItems = params.unpackedItems + amount
params.unpackedKilos = unpacked
if params.unpackedKilos >= params.kilos or spawned == false then
return
end
return time + 0.5
end
---@type UnpackCrateParam
local params = {
self = self,
groupsPerKilo = self._groupsPerKilo,
unpackedItems = 0,
kilosPerSecond = persistedKilos/30,
unpackedKilos = 0,
kilos = persistedKilos
}
timer.scheduleFunction(startUnpackingCrate, params, timer.getTime() + 5)
kilosRequired = kilosRequired - persistedKilos
end
local noLandingZone = self:GetNoLandingZone()
if kilosRequired and kilosRequired > 0 then
self._buildableMission = Spearhead.classes.stageClasses.missions.BuildableMission.new(database, logger, targetZone, noLandingZone, kilosRequired, crateType)
self._buildableMission:AddOnCrateDroppedOfListener(self)
else
self._buildableMission = nil
end
if self._buildableMission == nil then
self._buildableLogger:debug("No buildable mission for zone: " .. targetZone.name)
end
end
---@protected
function BuildableZone:StartBuildable()
self._buildableMission:SpawnActive()
end
---@protected
function BuildableZone:OnBuildingComplete() end
---@class UnpackCrateParam
---@field self BuildableZone
---@field kilos number
---@field groupsPerKilo number
---@field kilosPerSecond number
---@field unpackedItems number
---@field unpackedKilos number
---@param mission BuildableMission?
---@param kilos number
function BuildableZone:OnCrateDroppedOff(mission, kilos)
self._buildableLogger:debug("Crate dropped off in zone: " .. self._targetZone.name)
local timeToUnpack = (kilos / 500) * 15
---@type UnpackCrateParam
local params = {
self = self,
groupsPerKilo = self._groupsPerKilo,
unpackedItems = 0,
kilosPerSecond = kilos/timeToUnpack,
unpackedKilos = 0,
kilos = kilos
}
---@param params UnpackCrateParam
---@param time number
local startUnpackingCrate = function(params, time)
local unpacked = params.unpackedKilos + (params.kilosPerSecond * 2)
local alreadySpawned = params.unpackedItems / params.groupsPerKilo
local diff = unpacked - alreadySpawned
local amount = math.floor(diff * params.groupsPerKilo)
local spawned = params.self:SpawnAmount(amount)
params.unpackedItems = params.unpackedItems + amount
params.unpackedKilos = unpacked
if params.unpackedKilos >= params.kilos or spawned == false then
params.self:FinaliseCrate(params.kilos)
return
end
return time + 2
end
timer.scheduleFunction(startUnpackingCrate, params, timer.getTime() + 2)
end
---@param kilos number
function BuildableZone:FinaliseCrate(kilos)
self._receivedBuildingKilos = self._receivedBuildingKilos + kilos
Spearhead.classes.persistence.Persistence.SetZoneDeliveredKilos(self._targetZone.name, self._receivedBuildingKilos)
if self._receivedBuildingKilos >= self._requiredKilos then
self:OnBuildingComplete()
end
end
---@private
---@return SpearheadTriggerZone?
function BuildableZone:GetNoLandingZone()
---@type Array<Vec2>
local points = {}
for _, group in pairs(self._buildableGroups) do
for _, unitPos in pairs(group:GetAllUnitPositions()) do
table.insert(points, { x = unitPos.x, y = unitPos.z })
end
end
local vecs = Spearhead.Util.getConvexHull(points)
---@type SpearheadTriggerZone
local spearheadZone = {
name = self._targetZone.name .. "_noland",
location = self._targetZone.location,
verts = vecs,
radius = 0,
zone_type = "Polygon"
}
return spearheadZone
end
---comment
---@param amount number
---@return boolean
function BuildableZone:SpawnAmount(amount)
local function spawnOne()
for _, group in pairs(self._buildableGroups) do
if group:IsSpawned() == false then
group:Spawn()
return true
end
end
return nil
end
for i = 1, amount do
local spawned = spawnOne()
if spawned ~= true then
self._buildableLogger:debug("No more groups to spawn in zone: " .. self._targetZone.name)
return false
end
end
return true
end
if not Spearhead then Spearhead = {} end
Spearhead.classes = Spearhead.classes or {}
Spearhead.classes.stageClasses = Spearhead.classes.stageClasses or {}
Spearhead.classes.stageClasses.SpecialZones = Spearhead.classes.stageClasses.SpecialZones or {}
Spearhead.classes.stageClasses.SpecialZones.abstract = Spearhead.classes.stageClasses.SpecialZones.abstract or {}
Spearhead.classes.stageClasses.SpecialZones.abstract.BuildableZone = BuildableZone
@@ -63,8 +63,9 @@ Stage.StageColors = {
---@param logger Logger
---@param initData StageInitData
---@param missionPriority MissionPriority
---@param spawnManager SpawnManager
---@return Stage
function Stage:superNew(database, stageConfig, logger, initData, missionPriority)
function Stage:superNew(database, stageConfig, logger, initData, missionPriority, spawnManager)
logger:debug("[BaseStage] Initiating stage with name: " .. initData.stageZoneName)
@@ -109,7 +110,7 @@ function Stage:superNew(database, stageConfig, logger, initData, missionPriority
local farpNames = database:getFarpNamesInStage(self.zoneName)
for _, farpName in pairs(farpNames) do
local farp = Spearhead.classes.stageClasses.SpecialZones.FarpZone.New(database, logger, farpName)
local farp = Spearhead.classes.stageClasses.SpecialZones.FarpZone.New(database, logger, farpName, spawnManager)
table.insert(self._db.farps, farp)
end
@@ -141,7 +142,7 @@ function Stage:superNew(database, stageConfig, logger, initData, missionPriority
self._logger:debug("Found " .. Spearhead.Util.tableLength(missionZones) .. " mission zones for stage: " .. self.zoneName)
for _, missionZone in pairs(missionZones) do
local mission = Spearhead.classes.stageClasses.missions.ZoneMission.new(missionZone, self._missionPriority, database, logger, self)
local mission = Spearhead.classes.stageClasses.missions.ZoneMission.new(missionZone, self._missionPriority, database, logger, self, spawnManager)
if mission then
self._db.missionsByCode[mission.code] = mission
@@ -164,7 +165,7 @@ function Stage:superNew(database, stageConfig, logger, initData, missionPriority
---@type table<string, Array<Mission>>
local randomMissionByName = {}
for _, missionZoneName in pairs(randomMissionNames) do
local mission = Spearhead.classes.stageClasses.missions.ZoneMission.new(missionZoneName, self._missionPriority, database, logger, self)
local mission = Spearhead.classes.stageClasses.missions.ZoneMission.new(missionZoneName, self._missionPriority, database, logger, self, spawnManager)
if mission then
if randomMissionByName[mission.name] == nil then
randomMissionByName[mission.name] = {}
@@ -217,22 +218,22 @@ function Stage:superNew(database, stageConfig, logger, initData, missionPriority
local airbaseNames = database:getAirbaseNamesInStage(self.zoneName)
if airbaseNames ~= nil and type(airbaseNames) == "table" then
for _, airbaseName in pairs(airbaseNames) do
local airbase = Spearhead.classes.stageClasses.SpecialZones.StageBase.New(database, logger, airbaseName)
local airbase = Spearhead.classes.stageClasses.SpecialZones.StageBase.New(database, logger, airbaseName, spawnManager)
table.insert(self._db.airbases, airbase)
end
end
for _, samZoneName in pairs(database:getBlueSamsInStage(self.zoneName)) do
local blueSam = Spearhead.classes.stageClasses.SpecialZones.BlueSam.New(database, logger, samZoneName)
local blueSam = Spearhead.classes.stageClasses.SpecialZones.BlueSam.New(database, logger, samZoneName, spawnManager)
table.insert(self._db.blueSams, blueSam)
end
local miscGroups = database:getMiscGroupsAtStage(self.zoneName)
for _, groupName in pairs(miscGroups) do
local miscGroup = SpearheadGroup.New(groupName)
local miscGroup = SpearheadGroup.New(groupName, spawnManager, true)
table.insert(self._db.miscGroups, miscGroup)
Spearhead.DcsUtil.DestroyGroup(groupName)
miscGroup:Destroy()
end
end
+3 -2
View File
@@ -9,14 +9,15 @@ ExtraStage.__index = ExtraStage
---@param stageConfig StageConfig
---@param logger any
---@param initData StageInitData
---@param spawnManager SpawnManager
---@return ExtraStage
function ExtraStage.New(database, stageConfig, logger, initData)
function ExtraStage.New(database, stageConfig, logger, initData, spawnManager)
local Stage = Spearhead.classes.stageClasses.Stages.BaseStage.Stage
setmetatable(ExtraStage, Stage)
local self = setmetatable({}, { __index = ExtraStage }) --[[@as ExtraStage]]
self:superNew(database, stageConfig, logger, initData, "secondary")
self:superNew(database, stageConfig, logger, initData, "secondary", spawnManager)
self.OnPostBlueActivated = function (selfStage)
+3 -2
View File
@@ -9,14 +9,15 @@ PrimaryStage.__index = PrimaryStage
---@param stageConfig StageConfig
---@param logger any
---@param initData StageInitData
---@param spawnManager SpawnManager
---@return PrimaryStage
function PrimaryStage.New(database, stageConfig, logger, initData)
function PrimaryStage.New(database, stageConfig, logger, initData, spawnManager)
local Stage = Spearhead.classes.stageClasses.Stages.BaseStage.Stage
setmetatable(PrimaryStage, Stage)
local self = setmetatable({}, { __index = PrimaryStage }) --[[@as PrimaryStage]]
self:superNew(database, stageConfig, logger, initData, "primary")
self:superNew(database, stageConfig, logger, initData, "primary", spawnManager)
return self
end
+3 -2
View File
@@ -15,14 +15,15 @@ local WaitingStageInitData = {}
---@param stageConfig StageConfig
---@param logger any
---@param initData WaitingStageInitData
---@param spawnManager SpawnManager
---@return WaitingStage
function WaitingStage.New(database, stageConfig, logger, initData)
function WaitingStage.New(database, stageConfig, logger, initData, spawnManager)
local Stage = Spearhead.classes.stageClasses.Stages.BaseStage.Stage
setmetatable(WaitingStage, Stage)
local self = setmetatable({}, { __index = WaitingStage }) --[[@as WaitingStage]]
self:superNew(database, stageConfig, logger, initData, "none")
self:superNew(database, stageConfig, logger, initData, "none", spawnManager)
self._waitTimeSeconds = 5
if initData.waitingSeconds and initData.waitingSeconds > 5 then self._waitTimeSeconds = initData.waitingSeconds end
@@ -155,7 +155,7 @@ function BuildableMission:SpawnActive()
self._logger:debug("Spawning buildable mission: " .. self.code)
if self._noLandingZone == nil then
self._logger:error("No no landing zone found for mission: " .. self.code)
self._logger:error("No nolanding zone found for mission: " .. self.code)
return
end
@@ -65,8 +65,9 @@ MINIMAL_UNITS_ALIVE_RATIO = 0.21
---@param database Database
---@param logger Logger
---@param parentStage Stage
---@param spawnManager SpawnManager
---@return ZoneMission?
function ZoneMission.new(zoneName, priority, database, logger, parentStage)
function ZoneMission.new(zoneName, priority, database, logger, parentStage, spawnManager)
local Mission = Spearhead.classes.stageClasses.missions.baseMissions.Mission
ZoneMission.__index = ZoneMission
setmetatable(ZoneMission, Mission)
@@ -128,7 +129,7 @@ function ZoneMission.new(zoneName, priority, database, logger, parentStage)
if not missionData then return end
for _, groupName in pairs(missionData.BlueGroups) do
local spearheadGroup = SpearheadGroup.New(groupName)
local spearheadGroup = SpearheadGroup.New(groupName, spawnManager, true)
if spearheadGroup then
table.insert(self._missionGroups.blueGroups, spearheadGroup)
end
@@ -136,7 +137,7 @@ function ZoneMission.new(zoneName, priority, database, logger, parentStage)
end
for _, groupName in pairs(missionData.RedGroups) do
local spearheadGroup = SpearheadGroup.New(groupName)
local spearheadGroup = SpearheadGroup.New(groupName, spawnManager, true)
table.insert(self._missionGroups.redGroups, spearheadGroup)
local isGroupTarget = Spearhead.Util.startswith(string.lower(groupName), "tgt_")
+3 -3
View File
@@ -86,9 +86,9 @@ SpearheadConfig = {
--- enables or disables the persistence logic in spearhead
enabled = false,
--- sets the directory where the persistence file is stored
--- if nil then lfs.writedir() will be used.
--- which will
--- sets the directory where the persistence file is stored <br>
--- if nil then lfs.writedir()/Data will be used. <br>
--- which will Result in <Saved Games>/<DCS Saved Games Folder>/Data/
directory = nil ,
--- the filename of the persistence file. Should end with .json for convention, but any text extension should do.
+4
View File
@@ -16,6 +16,9 @@ assert(loadfile(classPath .. "spearhead_db.lua"))()
assert(loadfile(classPath .. "fleetClasses\\FleetGroup.lua"))()
assert(loadfile(classPath .. "fleetClasses\\GlobalFleetManager.lua"))()
assert(loadfile(classPath .. "helpers\\MizGroupsManager.lua"))()
assert(loadfile(classPath .. "helpers\\SpawnManager.lua"))()
assert(loadfile(classPath .. "configuration\\CapConfig.lua"))()
assert(loadfile(classPath .. "configuration\\StageConfig.lua"))()
@@ -39,6 +42,7 @@ assert(loadfile(classPath .. "stageClasses\\helpers\\SupplyConfig.lua"))()
assert(loadfile(classPath .. "stageClasses\\helpers\\SupplyUnitsTracker.lua"))()
assert(loadfile(classPath .. "stageClasses\\helpers\\BattleManager.lua"))()
assert(loadfile(classPath .. "stageClasses\\SpecialZones\\abstract\\BuildableZone.lua"))()
assert(loadfile(classPath .. "stageClasses\\SpecialZones\\StageBase.lua"))()
assert(loadfile(classPath .. "stageClasses\\SpecialZones\\BlueSam.lua"))()
assert(loadfile(classPath .. "stageClasses\\SpecialZones\\FarpZone.lua"))()
+2 -2
View File
@@ -64,7 +64,7 @@ SpearheadConfig = {
--- io and lfs cannot be sanitized in the MissionScripting.lua
--- enables or disables the persistence logic in spearhead
enabled = false,
enabled = true,
--- sets the directory where the persistence file is stored
--- if nil then lfs.writedir() will be used.
@@ -72,7 +72,7 @@ SpearheadConfig = {
directory = nil ,
--- the filename of the persistence file. Should end with .json for convention, but any text extension should do.
fileName = "Spearhead_Persistence_Dev.json"
fileName = "Spearhead_Persistence_Dev"
}
}
+5 -2
View File
@@ -33,11 +33,14 @@ else
standardLogger:info("Persistence disabled")
end
local spawnLogger = Spearhead.LoggerTemplate.new("SpawnManager", defaultLogLevel)
local spawnManager = Spearhead.classes.helpers.SpawnManager.new(spawnLogger)
local detectionLogger = Spearhead.LoggerTemplate.new("DetectionManager", defaultLogLevel)
local detectionManager = Spearhead.classes.capClasses.detection.DetectionManager.New(detectionLogger)
Spearhead.classes.capClasses.GlobalCapManager.start(databaseManager, capConfig, detectionManager, stageConfig, defaultLogLevel)
Spearhead.internal.GlobalStageManager:NewAndStart(databaseManager, stageConfig, defaultLogLevel)
Spearhead.classes.capClasses.GlobalCapManager.start(databaseManager, capConfig, detectionManager, stageConfig, defaultLogLevel, spawnManager)
Spearhead.internal.GlobalStageManager:NewAndStart(databaseManager, stageConfig, defaultLogLevel, spawnManager)
Spearhead.internal.GlobalFleetManager.start(databaseManager)
local SetStageDelayed = function(number, time)