airbases now also 'buildable' (#37)

Airbases added as buildables.
This commit is contained in:
2025-05-19 20:32:57 +02:00
committed by GitHub
parent 97f6d2d201
commit b96e5e709c
9 changed files with 254 additions and 67 deletions
Binary file not shown.
+13 -6
View File
@@ -487,29 +487,36 @@ do -- INIT DCS_UTIL
DCS_UTIL.__airbaseNamesById[tostring(airbase:getID())] = name
if name then
---@type Array<Vec2>
local relevantPoints = {}
for _, x in pairs(airbase:getRunways()) do
if x.position and x.position.x and x.position.z then
table.insert(relevantPoints, { x = x.position.x, z = x.position.z, y = 0 })
table.insert(relevantPoints, { x = x.position.x, y = x.position.z })
end
end
for _, x in pairs(airbase:getParking()) do
if x.vTerminalPos and x.vTerminalPos.x and x.vTerminalPos.z then
table.insert(relevantPoints, { x = x.vTerminalPos.x, z = x.vTerminalPos.z, y = 0 })
table.insert(relevantPoints, { x = x.vTerminalPos.x, y = x.vTerminalPos.z })
end
end
local points = UTIL.getConvexHull(relevantPoints)
local enlargedPoints = UTIL.enlargeConvexHull(points, 750)
DCS_UTIL.__airbaseZonesByName[name] = {
local triggerZone = {
name = name,
location = { x = airbase:getPoint().x, y = airbase:getPoint().z },
zone_type = "Polygon",
radius = 0,
verts = enlargedPoints
}
if SpearheadConfig and SpearheadConfig.debugEnabled == true then
DCS_UTIL.DrawZone(triggerZone, { r = 0, g = 1, b = 0, a = 1 }, { a = 0, r = 0, g = 1, b = 0 }, 1)
end
DCS_UTIL.__airbaseZonesByName[name] = triggerZone
end
end
end
@@ -732,10 +739,10 @@ do -- INIT DCS_UTIL
---comment
---@param airbaseName string
---@return SpearheadTriggerZone? zone { name,b zone_type, x, z, radius, verts }
---@return SpearheadTriggerZone? zone
function DCS_UTIL.getAirbaseZoneByName(airbaseName)
if string == nil then return nil end
return DCS_UTIL.__airbaseZonesByName[string]
if airbaseName == nil then return nil end
return DCS_UTIL.__airbaseZonesByName[airbaseName]
end
---maps the category name to the DCS group category
+40 -16
View File
@@ -49,6 +49,7 @@
---@field RedGroups Array<string>
---@field BlueGroups Array<string>
---@field supplyHubNames Array<string>
---@field buildingKilos number?
---@class BlueSamData
---@field groups Array<string>
@@ -196,6 +197,13 @@ function Database.New(Logger)
local number = tonumber(layer_object.text)
farpData.buildingKilos = number
end
local airbaseData = self:getAirbaseDataForDrawLayer(layer_object)
if airbaseData then
self._logger:debug("found airbase data for " .. layer_object.name)
local number = tonumber(layer_object.text)
airbaseData.buildingKilos = number
end
elseif Spearhead.Util.startswith(string.lower(layer_object.name), "completeat", true) == true then
local annotationData = self:getMissionMetaDataForDrawLayer(layer_object)
if annotationData then
@@ -347,22 +355,6 @@ function Database.New(Logger)
end
end
---@private
---@param baseName string
---@return AirbaseData
function Database:getOrCreateAirbaseData(baseName)
local baseData = self._tables.AirbaseDataPerAirfield[baseName]
if baseData == nil then
baseData = {
CapGroups = {},
RedGroups = {},
BlueGroups = {},
supplyHubNames = {}
}
self._tables.AirbaseDataPerAirfield[baseName] = baseData
end
return baseData
end
self:initAvailableUnits()
self:loadCapUnits()
@@ -511,6 +503,21 @@ function Database:getFarpDataForDrawLayer(layer_object)
return nil
end
---comment
---@param layer_object table
---@return AirbaseData?
function Database:getAirbaseDataForDrawLayer(layer_object)
for _, airbase in pairs(world.getAirbases()) do
local zone = Spearhead.DcsUtil.getAirbaseZoneByName(airbase:getName())
if zone and Spearhead.Util.is2dPointInZone({ x = layer_object.mapX, y = layer_object.mapY }, zone) == true then
return self:getOrCreateAirbaseData(airbase:getName())
end
end
return nil
end
---@private
function Database:getOrCreateBlueSamDataForZone(zoneName)
local blueSamData = self._tables.BlueSamDataPerZone[zoneName]
@@ -539,6 +546,23 @@ function Database:getOrCreateFarpDataForZone(zoneName)
return farpData
end
---@private
---@param baseName string
---@return AirbaseData
function Database:getOrCreateAirbaseData(baseName)
local baseData = self._tables.AirbaseDataPerAirfield[baseName]
if baseData == nil then
baseData = {
CapGroups = {},
RedGroups = {},
BlueGroups = {},
supplyHubNames = {}
}
self._tables.AirbaseDataPerAirfield[baseName] = baseData
end
return baseData
end
---@private
---@param layer_object table
---@return MissionAnnotations?
@@ -80,10 +80,13 @@ function BlueSam.New(database, logger, zoneName)
self._logger:debug("Buildable mission creating for zone: " .. zoneName .. " with crates: " .. self._buildableCrateKilos)
local noLandingZone = self:GetNoLandingZone()
self._buildableMission = Spearhead.classes.stageClasses.missions.BuildableMission.new(database, logger, zoneName, noLandingZone, self._buildableCrateKilos, "SAM_CRATE")
self._buildableMission:AddOnCrateDroppedOfListener(self)
self._buildableMission:AddMissionCompleteListener(self)
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
end
+10 -7
View File
@@ -66,14 +66,17 @@ function FarpZone.New(database, logger, zoneName)
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()
self._buildableMission = Spearhead.classes.stageClasses.missions.BuildableMission.new(database, logger, zoneName, noLandingZone, self._requiredBuildingKilos, "FARP_CRATE")
if self._buildableMission then
self._buildableMission:AddOnCrateDroppedOfListener(self)
self._buildableMission:AddMissionCompleteListener(self)
end
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
local totalGroups = Spearhead.Util.tableLength(self._groups)
self._groupsPerKilo = totalGroups / self._requiredBuildingKilos
end
end
end
if self._buildableMission == nil then
+156 -20
View File
@@ -1,6 +1,4 @@
---@class StageBase
---@class StageBase : OnCrateDroppedListener, MissionCompleteListener
---@field private _database Database
---@field private _logger Logger
---@field private _red_groups Array<SpearheadGroup>
@@ -9,6 +7,10 @@
---@field private _airbase Airbase?
---@field private _initialSide number?
---@field private _supplyHubs Array<SupplyHub>
---@field private _groupsPerKilo number
---@field private _requiredBuildingKilos number
---@field private _receivedBuildingKilos number
---@field private _buildableMission BuildableMission?
local StageBase = {}
---comment
@@ -17,7 +19,6 @@ local StageBase = {}
---@param airbaseName string
---@return StageBase?
function StageBase.New(databaseManager, logger, airbaseName)
StageBase.__index = StageBase
local self = setmetatable({}, StageBase)
@@ -33,13 +34,12 @@ function StageBase.New(databaseManager, logger, airbaseName)
self._initialSide = Spearhead.DcsUtil.getStartingCoalition(self._airbase)
do --init
local airbaseData = databaseManager:getAirbaseDataForZone(airbaseName)
if airbaseData == nil then
logger:error("Airbase data not found for airbase: " .. airbaseName)
return nil
end
---@type table<string, Vec3>
local redUnitsPos = {}
@@ -69,16 +69,17 @@ function StageBase.New(databaseManager, logger, airbaseName)
end
for _, supplyHubName in pairs(airbaseData.supplyHubNames) do
local supplyHub = Spearhead.classes.stageClasses.SpecialZones.SupplyHub.new(databaseManager, logger, supplyHubName)
local supplyHub = Spearhead.classes.stageClasses.SpecialZones.SupplyHub.new(databaseManager, logger,
supplyHubName)
if supplyHub then
table.insert(self._supplyHubs, supplyHub)
end
end
do -- check cleanup requirements
-- Checks is any of the units are withing range (5m) of another unit.
-- Checks is any of the units are withing range (5m) of another unit.
-- If so, make sure to add them to the cleanup list.
local cleanup_distance = 5
for blueUnitName, blueUnitPos in pairs(blueUnitsPos) do
@@ -90,14 +91,31 @@ function StageBase.New(databaseManager, logger, airbaseName)
end
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
end
end
return self
end
end
---@private
function StageBase:SpawnRedUnits()
---comment
---@param groups Array<SpearheadGroup>
local spawnAsync = function(groups)
@@ -122,14 +140,11 @@ function StageBase:CleanRedUnits()
Spearhead.DcsUtil.DestroyUnit(unitName)
Spearhead.DcsUtil.CleanCorpse(unitName)
end
end
end
---@private
function StageBase:SpawnBlueUnits()
---comment
---@param groups Array<SpearheadGroup>
local spawnAsync = function(groups)
@@ -152,25 +167,146 @@ function StageBase:ActivateRedStage()
end
function StageBase:ActivateBlueStage()
self:CleanRedUnits()
if self._buildableMission and self._buildableMission:getState() ~= "COMPLETED" then
self._buildableMission:SpawnActive()
return
end
self:FinaliseBlueStage()
end
function StageBase:FinaliseBlueStage()
if self._initialSide == 2 and self._airbase then
self._airbase:setCoalition(2)
end
self:CleanRedUnits()
self:SpawnBlueUnits()
for _, hub in pairs(self._supplyHubs) do
hub:Activate()
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
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
if Spearhead.classes.stageClasses.SpecialZones == nil then Spearhead.classes.stageClasses.SpecialZones = {} end
Spearhead.classes.stageClasses.SpecialZones.StageBase = StageBase
@@ -358,12 +358,16 @@ function MissionCommandsHelper:AddSupplyHubCommandsIfApplicable(groupID)
missionCommands.addCommandForGroup(groupID, "Load FARP Crate (2000)", path, loadCargoCommand, farpParams2000)
---@type LoadCargoCommandParams
local samParms1000 = { unitID = unit:getID(), groupID = group:getID(), crateType = "FARP_CRATE_1000", supplyUnitsTracker = self._supplyUnitsTracker }
local samParms1000 = { unitID = unit:getID(), groupID = group:getID(), crateType = "SAM_CRATE_2000", supplyUnitsTracker = self._supplyUnitsTracker }
missionCommands.addCommandForGroup(groupID, "Load SAM Crate (1000)", path, loadCargoCommand, samParms1000)
---@type LoadCargoCommandParams
local samParms2000 = { unitID = unit:getID(), groupID = group:getID(), crateType = "FARP_CRATE_2000", supplyUnitsTracker = self._supplyUnitsTracker }
local samParms2000 = { unitID = unit:getID(), groupID = group:getID(), crateType = "SAM_CRATE_2000", supplyUnitsTracker = self._supplyUnitsTracker }
missionCommands.addCommandForGroup(groupID, "Load SAM Crate (2000)", path, loadCargoCommand, samParms2000)
---@type LoadCargoCommandParams
local samParms2000 = { unitID = unit:getID(), groupID = group:getID(), crateType = "AIRBASE_CRATE_2000", supplyUnitsTracker = self._supplyUnitsTracker }
missionCommands.addCommandForGroup(groupID, "Airbase Crate (2000)", path, loadCargoCommand, samParms2000)
end
function MissionCommandsHelper:AddCargoCommands(groupID)
@@ -2,6 +2,7 @@
---@alias SupplyType
---| "FARP_CRATE"
---| "SAM_CRATE"
---| "AIRBASE_CRATE"
---@alias CrateType
---| "FARP_CRATE_500"
@@ -10,6 +11,7 @@
---| "SAM_CRATE_500"
---| "SAM_CRATE_1000"
---| "SAM_CRATE_2000"
---| "AIRBASE_CRATE_2000"
---@class SupplyConfig
---@field type SupplyType
@@ -55,6 +57,12 @@ local SupplyConfig = {
displayName = "SAM Crate (2000)",
staticType = "container_cargo",
},
["AIRBASE_CRATE_2000"] = {
type = "AIRBASE_CRATE",
weight = 2000,
displayName = "Airbase Crate (2000)",
staticType = "container_cargo",
},
}
---@class MaxLoadConfig
@@ -4,7 +4,7 @@
---@field private _requiredKilos number
---@field private _droppedKilos number
---@field private _crateType SupplyType
---@field private _targetZoneName string
---@field private _targetZone SpearheadTriggerZone
---@field private _database Database
---@field private _onCrateDroppedOfListeners Array<OnCrateDroppedListener>
---@field private _markIDsPerGroup table<string, number>
@@ -20,19 +20,19 @@ BuildableMission.__index = BuildableMission
---@field OnCrateDroppedOff fun(self:OnCrateDroppedListener, mission:BuildableMission, kilos:number)
---@param database Database
---@param targetZoneName string
---@param targetZone SpearheadTriggerZone
---@param requiredKilos number
---@param requiredCrateType SupplyType
---@param noLandingZone SpearheadTriggerZone?
---@param logger Logger
function BuildableMission.new(database, logger, targetZoneName, noLandingZone, requiredKilos, requiredCrateType)
function BuildableMission.new(database, logger, targetZone, noLandingZone, requiredKilos, requiredCrateType)
local Mission = Spearhead.classes.stageClasses.missions.baseMissions.Mission
setmetatable(BuildableMission, Mission)
local self = setmetatable({}, { __index = BuildableMission })
self._targetZoneName = targetZoneName
self._targetZone = targetZone
self._database = database
self._requiredKilos = requiredKilos
self._droppedKilos = 0
@@ -46,7 +46,7 @@ function BuildableMission.new(database, logger, targetZoneName, noLandingZone, r
---@type SpearheadTriggerZone
local dropOfZone = {
name = targetZoneName .. "_dropZone",
name = targetZone.name .. "_dropZone",
zone_type = "Polygon",
radius = 0,
verts = enlarged,
@@ -66,7 +66,7 @@ function BuildableMission.new(database, logger, targetZoneName, noLandingZone, r
type = "FARP"
end
self.zoneName = targetZoneName .. "_supply"
self.zoneName = targetZone.name .. "_supply"
self._logger = logger
self._onCrateDroppedOfListeners = {}
self._completeListeners = {}
@@ -74,13 +74,8 @@ function BuildableMission.new(database, logger, targetZoneName, noLandingZone, r
self._supplyUnitsTracker = Spearhead.classes.stageClasses.helpers.SupplyUnitsTracker.getOrCreate(logger.LogLevel)
self._state = "NEW"
local spearheadZone = Spearhead.DcsUtil.getZoneByName(targetZoneName)
if spearheadZone == nil then
logger:error("Zone not found: " .. targetZoneName)
return nil
end
self.location = spearheadZone.location
self.location = targetZone.location
self.missionType = "LOGISTICS"
self.missionTypeDisplay = "LOGISTICS"
@@ -110,6 +105,8 @@ function BuildableMission:ShowBriefing(groupID)
local siteType = "FARP"
if self._crateType == "SAM_CRATE" then
siteType = "SAM site"
elseif self._crateType == "AIRBASE_CRATE" then
siteType = "airbase"
end
local briefing = "Mission [" .. self.code .. "] " .. self.name ..
@@ -146,6 +143,11 @@ end
function BuildableMission:SpawnActive()
if self._state ~= "NEW" then
self._logger:debug("Mission already spawned: " .. self.code)
return
end
self._logger:debug("Spawning buildable mission: " .. self.code)
if self._noLandingZone == nil then