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
+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