Buildable (#36)

- Buildable missions now working. 
- FARPS and BLUESAM zones are now buildable. 
- Secondary Logistics missions are created as buildables become available. 
- Logistic missions can be "marked" with a MAP marker. 
- Supplies can only be picked up in a SUPPLYHUB zone. 
- SUPPLYHUB zones are active once their parents are active. A supplyhub parent can be oneof: FARP, AIRBASE, STAGE. 
- Required supplies for a zone can be configured in kilos. 
- Crates can be picked up with 1000 or 2000 kilo per crate. 
- Max load for a helicopter is set based on public data. 
- TO COME: Sling load options
This commit is contained in:
2025-05-18 20:08:26 +02:00
committed by GitHub
parent 5b6ea49217
commit 3e65df9d20
21 changed files with 1708 additions and 181 deletions
+8 -5
View File
@@ -25,9 +25,12 @@
---@field x number directed to the north
---@field y number directed to the east
---@field z number directed up (away from ground)
---@class Position : Vec3
---@class Position
---@field p Vec3 Point on map
---@field x Vec3 Direction vector
---@field y Vec3 Direction vector
---@field z Vec3 Direction vector
---@class Array<T>: { [number]: T }
@@ -216,14 +219,14 @@ do -- world
---@field min Vec3
---@field max Vec3
---@class Sphere
---@class Sphere : Volume
---@field params SphereParams
---@class SphereParams
---@field point Vec3
---@field radius number
---@class VolumePyramid
---@class VolumePyramid : Volume
---@field params VolumePyramidParams
---@class VolumePyramidParams
@@ -348,7 +351,7 @@ do -- trigger
---@class MarkCommands
---@field markToAll fun(id: number, text:string, point:Vec3, readOnly: boolean?, message: string?) Adds a mark point to all on the F10 map with attached text.
---@field markToCoalition fun(id: number, text: string, point: Vec3, coalitionID: CoalitionSide, readOnly: boolean?, message: string) Adds a mark point to a coalition on the F10 map with attached text.
---@field markToGroup fun(id: number, text: string, point: Vec3, groupID: number, readOnly: boolean?, message: string) Adds a mark point to a group on the F10 map with attached text.
---@field markToGroup fun(id: number, text: string, point: Vec3, groupID: number, readOnly: boolean?, message: string?) Adds a mark point to a group on the F10 map with attached text.
---@field removeMark fun(id: number) Removes a mark panel from the f10 map
---@field markupToAll fun(shapeID: ShapeId, coalition: DrawCoalition, id: number, ... : any) Complex parameters. <br/> See: https://wiki.hoggitworld.com/view/DCS_func_markupToAll
---@field lineToAll fun(coalition: DrawCoalition, id: number, startPoint:Vec3, endPoint: Vec3, color:table, lineType: LineType, readonly: boolean?, message: string?) Creates a line on the F10 map from one point to another.
+40
View File
@@ -78,6 +78,8 @@
</ul>
</li>
<li><a href="#randomization" class="side-nav-h2">Randomization</a></li>
<li><a href="#in-stage-dependencies" class="side-nav-h2">In stage dependencies</a></li>
<li><a href="#buildable" class="side-nav-h2">Buildables</a></li>
<li><a href="#runway-bombing" class="side-nav-h2">Runway Bombing</a></li>
</ul>
</div>
@@ -164,6 +166,22 @@
</p>
<h3 id="supply-hub">Supply Hub</h3>
<p>
<strong>Format:</strong> <span class="inline-lua"><span class="lua-variable">SUPPLYHUB_[Name]</span></span> <br />
<strong>Format:</strong> <span class="inline-lua"><span class="lua-variable">SUPPLYHUB_A_[Name]</span></span> <br />
<strong>Example:</strong> <span class="inline-lua"><span class="lua-variable">SUPPLYHUB_HOUSTON</span></span> <br/> <br/>
Supply hubs are used as zones to pick up logistics for logistic missions. <br/>
They are activated when their parent is activated. <br/>
Activated depends on what it's parent is. <br/>
If it's a stage. It can be SUPPLYHUB_A_[name] when you want it to be active when the stage is initiated. (red state) or SUPPLYHUB_B_ it you want it activated when it's BLUE only. <br/>
If you place the Supply Hub trigger zone inside of a FARP zone the supply hub will activate when the FARP zone is activated. <br/>
If the FARP needs to be built first, then that means the supply hub also is activated later. <br/>
NOTE: The supply hub does not spawn anything, but merely give options to pick up logistics.
</p>
<h2 id="mission-types">Mission Types</h2>
<p>
@@ -258,6 +276,28 @@
The mission will be "pre-activated" (spawned) but won't have a f10 mission menu until the mission(s) it depends on is completed. <br/>
</p>
<h2 id="buildable">Buildables</h2>
<p>
Current buildables: <br/>
<span class="inline-lua"><span class="lua-variable">FARP_</span></span> <br/>
<span class="inline-lua"><span class="lua-variable">BLUESAM_</span></span> <br/>
<br/>
Buildables are zones or objects that first require logistics. <br/>
Eg. A forward SAM site or FARP requires logistic crates before it can be built. <br/>
To make an item buildable add a text box inside of the trigger zone and name it: <span class="inline-lua"><span class="lua-variable">buildable_[freeform]</span></span> <br/>
Then in the text box type amount of kilo's you want to be transfered before the logistisc mission is complete. <br/>
The base will slowly build up with each crate giving both a nice view of it happening AND it's good for performance as there's not a big addition of objects at once. <br/>
Right now a crate takes 15 seconds to unpack per 500kg. Meaning 2 crates of 1000kg will be faster than 1 crate of 2000kg. <br/>
All helicopters have a max limit set based on quick google searches and cannot be changed but the ME at this point: <br/>
UH-1H : 2000kg <br/>
Mi-8 : 4000kg <br/>
CH-47 : 10000kg <br/>
MI-24 : 2000kg <br/>
</p>
<h2>Runway Bombing</h2>
<p>
Runway bombing can be a very effective OCA tactic. With Spearhead we've tried adding as much logic and detail to it so it will feel as engaging as possible. <br/>
Binary file not shown.
+5 -1
View File
@@ -32,7 +32,11 @@ function CapBase.new(airbaseName, database, logger, capConfig, stageConfig, runw
CapBase.__index = CapBase
local self = setmetatable({}, { __index = CapBase }) --[[@as CapBase]]
self.groupNames = database:getCapGroupsAtAirbase(airbaseName)
local baseData = database:getAirbaseDataForZone(airbaseName)
local groupNames = {}
if baseData then groupNames = baseData.CapGroups or {} end
self.groupNames = groupNames
self.database = database
self.runwayBombingTracker = runwayBombingTracker
self.runwayStrikeMissions = {}
-5
View File
@@ -12,10 +12,5 @@ do -- mission aliases
---@class Array<T>: { [integer]: T }
--- @class Position
--- @field x number x position (Top-Down on Map)
--- @field y number y altitude
--- @field z number z position (Left-Right on Map)
end
+2 -2
View File
@@ -8,7 +8,7 @@ do
---@class DeathState
---@field isDead boolean
---@field pos Position
---@field pos Vec3
---@field heading number
---@field type string
---@field country_id integer
@@ -204,7 +204,7 @@ do
---Pass the unit to be saved as "dead"
---@param name string
---@param position Position { x, y ,z }
---@param position Vec3 { x, y ,z }
---@param heading number
---@param type string
---@param country_id number
+50
View File
@@ -206,6 +206,22 @@ do -- INIT UTIL
return false
end
---@param point Vec2
---@param zone SpearheadTriggerZone
function UTIL.is2dPointInZone(point, zone)
if zone.zone_type == "Polygon" and zone.verts then
if UTIL.IsPointInPolygon(zone.verts, point.x, point.y) == true then
return true
end
else
if (((point.x - zone.location.x) ^ 2 + (point.y - zone.location.y) ^ 2) ^ 0.5 <= zone.radius) then
return true
end
end
return false
end
---comment
---@param points Array<Vec2> points
---@return Array<Vec2> hullPoints
@@ -974,6 +990,27 @@ do -- INIT DCS_UTIL
return drawID
end
local _markID = 200
---@param groupID number
---@param text string
---@param location Vec3
---@return number markID
function DCS_UTIL.AddMarkToGroup(groupID, text, location)
_markID = _markID + 1
trigger.action.markToGroup(_markID, text, location, groupID, true, nil)
return _markID
end
---@param markId number
function DCS_UTIL.RemoveMark(markId)
if markId ~= nil then
trigger.action.removeMark(markId)
end
end
---comment
---@param drawID number
---@param lineColor DrawColor
@@ -1092,6 +1129,19 @@ do -- INIT DCS_UTIL
end
end
---@param unitID number
---@return Unit?
function DCS_UTIL.GetPLayerUnitByID(unitID)
for i = 0, 2 do
local players = coalition.getPlayers(i)
for key, unit in pairs(players) do
if unit and unit:getID() == unitID then
return unit
end
end
end
end
DCS_UTIL.__INIT();
end
Spearhead.DcsUtil = DCS_UTIL
+183 -68
View File
@@ -11,6 +11,7 @@
---@field CapDataPerStageNumber table<integer, CapData> table<StageNumber, table>
---@field CarrierRouteZones Array<string> All Carrier routes zones
---@field BlueSams Array<string> All blue sam zones
---@field SupplyHubZones Array<string> All supply hub zones
---@field MissionAnnotations table<string, MissionAnnotations> table<ZoneName, MissionAnnotations>
---@field AirbaseDataPerAirfield table<string, AirbaseData>
---@field BlueSamDataPerZone table<string, BlueSamData>
@@ -39,15 +40,19 @@
---@field RandomMissionZones Array<string>
---@field StageIndex string
---@field BlueSamZones Array<string>
---@field SupplyHubZones Array<string>
---@field SupplyHubZonesInFarp table<string, string>
---@field MiscGroups Array<string>
---@class AirbaseData
---@field CapGroups Array<string>
---@field RedGroups Array<string>
---@field BlueGroups Array<string>
---@field supplyHubNames Array<string>
---@class BlueSamData
---@field Groups Array<string>
---@field groups Array<string>
---@field buildingKilos number?
---@class MissionZoneData
---@field Groups Array<string>
@@ -58,6 +63,8 @@
---@class FarpZoneData
---@field groups Array<string>
---@field padNames Array<string>
---@field buildingKilos number?
---@field supplyHubNames Array<string>
---@class Database
---@field private _tables DatabaseTables
@@ -87,7 +94,8 @@ function Database.New(Logger)
MissionZoneData = {},
FarpZoneData = {},
missionCodes = {},
MissionAnnotations = {}
MissionAnnotations = {},
SupplyHubZones = {}
}
Database.__index = Database
@@ -126,7 +134,9 @@ function Database.New(Logger)
FarpZones = {},
MissionZones = {},
RandomMissionZones = {},
MiscGroups = {}
MiscGroups = {},
SupplyHubZones = {},
SupplyHubZonesInFarp = {}
}
self._tables.StageZones[zone_name] = stageData
end
@@ -161,25 +171,37 @@ function Database.New(Logger)
if string.lower(split_string[1]) == "bluesam" then
table.insert(self._tables.BlueSams, zone_name)
end
if string.lower(split_string[1]) == "supplyhub" then
table.insert(self._tables.SupplyHubZones, zone_name)
end
end
end
self._logger:debug("initiated zone tables, continuing with descriptions")
do --load markers
if env.mission.drawings and env.mission.drawings.layers then
for i, layer in pairs(env.mission.drawings.layers) do
if string.lower(layer.name) == "author" then
for key, layer_object in pairs(layer.objects) do
if Spearhead.Util.startswith(string.lower(layer_object.name), "buildable", true) == true then
local blueSamData = self:getBlueSamDataForDrawLayer(layer_object)
if blueSamData then
local number = tonumber(layer_object.text)
blueSamData.buildingKilos = number
end
if Spearhead.Util.startswith(string.lower(layer_object.name), "completeat", true) == true then
local farpData = self:getFarpDataForDrawLayer(layer_object)
if farpData then
local number = tonumber(layer_object.text)
farpData.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
local number = tonumber(layer_object.text)
if number and number > 1 then
number = number / 100
end
annotationData.completeAt = number
end
@@ -204,6 +226,12 @@ function Database.New(Logger)
end
end
---@type table<string, boolean>
local availableSupplyHubs = {}
for _, supplyHubZoneName in pairs(self._tables.SupplyHubZones) do
availableSupplyHubs[supplyHubZoneName] = true
end
for _, stageZoneName in pairs(self._tables.StageZoneNames) do
local stageData = self._tables.StageZones[stageZoneName]
if stageData then
@@ -214,6 +242,63 @@ function Database.New(Logger)
end
end
--- fill farp zones
for _, farpZoneName in pairs(self._tables.AllFarpZones) do
if Spearhead.DcsUtil.isZoneInZone(farpZoneName, stageZoneName) then
table.insert(stageData.FarpZones, farpZoneName)
for hubZoneName, available in pairs(availableSupplyHubs) do
if available == true and Spearhead.DcsUtil.isZoneInZone(hubZoneName, farpZoneName) == true then
local farpZoneData = self:getOrCreateFarpDataForZone(farpZoneName)
if farpZoneData then
table.insert(farpZoneData.supplyHubNames, hubZoneName)
availableSupplyHubs[hubZoneName] = false
end
end
end
end
end
-- fill airbases
for _, airbase in pairs(world.getAirbases()) do
local point = airbase:getPoint()
if Spearhead.DcsUtil.isPositionInZone(point.x, point.z, stageZoneName) == true then
if airbase:getDesc().category == 0 then
table.insert(stageData.AirbaseNames, airbase:getName())
local airbaseZone = Spearhead.DcsUtil.getAirbaseZoneByName(airbase:getName())
for hubZoneName, available in pairs(availableSupplyHubs) do
local zone = Spearhead.DcsUtil.getZoneByName(hubZoneName)
if zone and airbaseZone then
if available == true and Spearhead.Util.is2dPointInZone(zone.location, airbaseZone) == true then
local airbaseData = self:getOrCreateAirbaseData(airbase:getName())
if airbaseData then
table.insert(airbaseData.supplyHubNames, hubZoneName)
availableSupplyHubs[hubZoneName] = false
end
end
end
end
end
end
end
-- fill supply hubs
for supplyHubZone, available in pairs(availableSupplyHubs) do
if available == true and Spearhead.DcsUtil.isZoneInZone(supplyHubZone, stageZoneName) == true then
table.insert(stageData.SupplyHubZones, supplyHubZone)
end
end
for _, farpZoneName in pairs(stageData.FarpZones) do
for _, supplyHubZone in pairs(self._tables.SupplyHubZones) do
if Spearhead.DcsUtil.isZoneInZone(supplyHubZone, farpZoneName) == true then
stageData.SupplyHubZonesInFarp[supplyHubZone] = farpZoneName
end
end
end
-- fill missions
for key, missionZone in pairs(self._tables.MissionZones) do
if Spearhead.DcsUtil.isZoneInZone(missionZone, stageZoneName) == true then
@@ -227,24 +312,6 @@ function Database.New(Logger)
table.insert(stageData.RandomMissionZones, missionZone)
end
end
--- fill farp zones
for _, farpZoneName in pairs(self._tables.AllFarpZones) do
if Spearhead.DcsUtil.isZoneInZone(farpZoneName, stageZoneName) then
table.insert(stageData.FarpZones, farpZoneName)
end
end
-- fill airbases
for _, airbase in pairs(world.getAirbases()) do
local point = airbase:getPoint()
if Spearhead.DcsUtil.isPositionInZone(point.x, point.z, stageZoneName) == true then
if airbase:getDesc().category == 0 then
table.insert(stageData.AirbaseNames, airbase:getName())
end
end
end
end
end
@@ -268,7 +335,8 @@ function Database.New(Logger)
if self._tables.FarpZoneData[farpZoneName] == nil then
self._tables.FarpZoneData[farpZoneName] = {
groups = {},
padNames = {}
padNames = {},
supplyHubNames = {}
}
end
local position = airbase:getPoint()
@@ -288,7 +356,8 @@ function Database.New(Logger)
baseData = {
CapGroups = {},
RedGroups = {},
BlueGroups = {}
BlueGroups = {},
supplyHubNames = {}
}
self._tables.AirbaseDataPerAirfield[baseName] = baseData
end
@@ -417,13 +486,65 @@ function Database:initAvailableUnits()
end
end
---comment
---@private
---@param layer_object table
---@return BlueSamData?
function Database:getBlueSamDataForDrawLayer(layer_object)
for _, zonename in pairs(self._tables.BlueSams) do
if Spearhead.DcsUtil.isPositionInZone(layer_object.mapX, layer_object.mapY, zonename) == true then
return self:getOrCreateBlueSamDataForZone(zonename)
end
end
return nil
end
---@private
---@param layer_object table
---@return FarpZoneData?
function Database:getFarpDataForDrawLayer(layer_object)
for _, zonename in pairs(self._tables.AllFarpZones) do
if Spearhead.DcsUtil.isPositionInZone(layer_object.mapX, layer_object.mapY, zonename) == true then
return self:getOrCreateFarpDataForZone(zonename)
end
end
return nil
end
---@private
function Database:getOrCreateBlueSamDataForZone(zoneName)
local blueSamData = self._tables.BlueSamDataPerZone[zoneName]
if blueSamData == nil then
blueSamData = {
groups = {},
buildingCrates = nil
}
self._tables.BlueSamDataPerZone[zoneName] = blueSamData
end
return blueSamData
end
---@private
function Database:getOrCreateFarpDataForZone(zoneName)
local farpData = self._tables.FarpZoneData[zoneName]
if farpData == nil then
farpData = {
padNames = {},
groups = {},
buildingCrates = nil,
supplyHubNames = {}
}
self._tables.FarpZoneData[zoneName] = farpData
end
return farpData
end
---@private
---@param layer_object table
---@return MissionAnnotations?
function Database:getMissionMetaDataForDrawLayer(layer_object)
for _, zonename in pairs(self._tables.MissionZones) do
if Spearhead.DcsUtil.isPositionInZone(layer_object.mapX, layer_object.mapY, zonename) == true then
if self._tables.MissionAnnotations[zonename] == nil then
self._tables.MissionAnnotations[zonename] = {
description = nil,
@@ -437,7 +558,6 @@ function Database:getMissionMetaDataForDrawLayer(layer_object)
for _, zonename in pairs(self._tables.RandomMissionZones) do
if Spearhead.DcsUtil.isPositionInZone(layer_object.mapX, layer_object.mapY, zonename) == true then
if self._tables.MissionAnnotations[zonename] == nil then
self._tables.MissionAnnotations[zonename] = {
description = nil,
@@ -487,13 +607,11 @@ end
function Database:loadBlueSamUnits()
local all_groups = Spearhead.DcsUtil.getAllGroupNames()
for _, blueSamZone in pairs(self._tables.BlueSams) do
self._tables.BlueSamDataPerZone[blueSamZone] = {
Groups = {}
}
local samData = self:getOrCreateBlueSamDataForZone(blueSamZone)
local groups = Spearhead.DcsUtil.getGroupsInZone(all_groups, blueSamZone)
for _, groupName in pairs(groups) do
is_group_taken[groupName] = true
table.insert(self._tables.BlueSamDataPerZone[blueSamZone].Groups, groupName)
table.insert(samData.groups, groupName)
end
end
end
@@ -533,17 +651,12 @@ end
function Database:loadFarpGroups()
local all_groups = getAvailableGroups()
for _, farpZone in pairs(self._tables.AllFarpZones) do
if self._tables.FarpZoneData[farpZone] == nil then
self._tables.FarpZoneData[farpZone] = {
groups = {},
padNames = {}
}
end
local farpzoneData = self:getOrCreateFarpDataForZone(farpZone)
local groups = Spearhead.DcsUtil.getGroupsInZone(all_groups, farpZone)
for _, groupName in pairs(groups) do
is_group_taken[groupName] = true
table.insert(self._tables.FarpZoneData[farpZone].groups, groupName)
table.insert(farpzoneData.groups, groupName)
end
end
end
@@ -628,7 +741,6 @@ function Database:loadMiscGroupsInStages()
end
end
---@return string?
function Database:getMissionBriefingForMissionZone(missionZoneName)
if not self._tables.MissionAnnotations[missionZoneName] then
@@ -638,7 +750,6 @@ function Database:getMissionBriefingForMissionZone(missionZoneName)
return self._tables.MissionAnnotations[missionZoneName].description
end
---@param missionZoneName string
---@return Array<string>
function Database:getMissionDependencies(missionZoneName)
@@ -757,7 +868,6 @@ function Database:getGroupsForMissionZone(missionZoneName)
return missionZoneData.Groups
end
---@param stageName string
---@return table result airbase Names
function Database:getAirbaseNamesInStage(stageName)
@@ -780,14 +890,15 @@ function Database:getFarpDataForZone(farpZoneName)
return farpData
end
---@param airbaseName string
---@return Array<string>
function Database:getCapGroupsAtAirbase(airbaseName)
local airbaseData = self._tables.AirbaseDataPerAirfield[airbaseName]
if not airbaseData then return {} end
return airbaseData.CapGroups
---@return AirbaseData?
---@param baseName string
function Database:getAirbaseDataForZone(baseName)
local baseData = self._tables.AirbaseDataPerAirfield[baseName]
if not baseData then return nil end
return baseData
end
---@param stageName string
---@return Array<string>
function Database:getBlueSamsInStage(stageName)
@@ -796,29 +907,33 @@ function Database:getBlueSamsInStage(stageName)
return stageData.BlueSamZones
end
---@param stageName string
---@return Array<string>
function Database:getSupplyHubsInStage(stageName)
local stageData = self._tables.StageZones[stageName]
if not stageData then return {} end
return stageData.SupplyHubZones
end
---comment
---@param stageName any
---@param supplyZoneName any
---@return nil
function Database:getFarpDependencyForSupplyHub(stageName, supplyZoneName)
local stageData = self._tables.StageZones[stageName]
if not stageData then return nil end
return stageData.SupplyHubZonesInFarp[supplyZoneName]
end
---@param samZone string
---@return Array<string>
function Database:getBlueSamGroupsInZone(samZone)
local blueSamData = self._tables.BlueSamDataPerZone[samZone]
if not blueSamData then return {} end
return blueSamData.Groups
---@return BlueSamData?
function Database:getBlueSamDataForZone(samZone)
return self._tables.BlueSamDataPerZone[samZone]
end
---@param airbaseName string
---@return Array<string>
function Database:getRedGroupsAtAirbase(airbaseName)
local airbaseData = self._tables.AirbaseDataPerAirfield[airbaseName]
if not airbaseData then return {} end
return airbaseData.RedGroups
end
---@param airbaseName string
---@return Array<string>
function Database:getBlueGroupsAtAirbase(airbaseName)
local airbaseData = self._tables.AirbaseDataPerAirfield[airbaseName]
if not airbaseData then return {} end
return airbaseData.BlueGroups
end
function Database:getMiscGroupsAtStage(stageName)
local stageZone = self._tables.StageZones[stageName]
@@ -21,6 +21,10 @@ function SpearheadGroup.New(groupName)
return self
end
function SpearheadGroup:IsSpawned()
return self._isSpawned
end
function SpearheadGroup:SpawnCorpsesOnly()
if self._isSpawned == true then return end
@@ -146,6 +150,26 @@ function SpearheadGroup:GetUnits()
return result
end
---@return Array<Vec3>
function SpearheadGroup:GetAllUnitPositions()
local result = {}
if self._isStatic == true then
local staticObject = StaticObject.getByName(self.groupName)
if staticObject then
table.insert(result, staticObject:getPoint())
end
else
local group = Group.getByName(self.groupName)
if not group then return {} end
for _, unit in pairs(group:getUnits()) do
table.insert(result, unit:getPoint())
end
end
return result
end
if not Spearhead.classes then Spearhead.classes = {} end
if not Spearhead.classes.stageClasses then Spearhead.classes.stageClasses = {} end
if not Spearhead.classes.stageClasses.Groups then Spearhead.classes.stageClasses.Groups = {} end
+119 -35
View File
@@ -1,13 +1,21 @@
---@class BlueSam
---@class BlueSam : OnCrateDroppedListener, MissionCompleteListener
---@field Activate fun(self: BlueSam)
---@field private _database Database
---@field private _logger Logger
---@field private _zoneName string
---@field private _blueGroups Array<SpearheadGroup>
---@field private _cleanupUnits table<string, boolean>
---@field private _buildableCrateKilos number?
---@field private _receivedKilos number?
---@field private _unitsPerCrate number?
---@field private _buildableMission BuildableMission?
local BlueSam = {}
---@param database Database
---@param logger Logger
---@param zoneName string
---@return BlueSam?
function BlueSam.New(database, logger, zoneName)
BlueSam.__index = BlueSam
local self = setmetatable({}, BlueSam)
@@ -19,56 +27,114 @@ function BlueSam.New(database, logger, zoneName)
self._blueGroups = {}
self._cleanupUnits = {}
do
local groups = database:getBlueSamGroupsInZone(zoneName)
local blueSamData = database:getBlueSamDataForZone(zoneName)
---@type table<string, Vec3>
local blueUnitsPos = {}
if blueSamData == nil then
logger:error("Blue SAM data not found for zone: " .. zoneName)
return nil
end
---@type table<string, Vec3>
local redUnitsPos = {}
self._buildableCrateKilos = blueSamData.buildingKilos
self._receivedKilos = 0
for _, groupName in pairs(groups) do
local SpearheadGroup = Spearhead.classes.stageClasses.Groups.SpearheadGroup.New(groupName)
if SpearheadGroup then
if SpearheadGroup:GetCoalition() == 2 then
table.insert(self._blueGroups, SpearheadGroup)
end
---@type table<string, Vec3>
local blueUnitsPos = {}
---@type table<string, Vec3>
local redUnitsPos = {}
for _, unit in pairs(SpearheadGroup:GetUnits()) do
if SpearheadGroup:GetCoalition() == 1 then
table.insert(blueUnitsPos, unit:getPoint())
elseif SpearheadGroup:GetCoalition() == 2 then
table.insert(redUnitsPos, unit:getPoint())
end
end
for _, groupName in pairs(blueSamData.groups) do
local SpearheadGroup = Spearhead.classes.stageClasses.Groups.SpearheadGroup.New(groupName)
if SpearheadGroup then
if SpearheadGroup:GetCoalition() == 2 then
table.insert(self._blueGroups, SpearheadGroup)
end
SpearheadGroup:Destroy()
end
do -- check cleanup requirements
-- 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
for redUnitName, redUnitPos in pairs(redUnitsPos) do
local distance = Spearhead.Util.VectorDistance3d(blueUnitPos, redUnitPos)
if distance <= cleanup_distance then
self._cleanupUnits[redUnitName] = true
end
for _, unit in pairs(SpearheadGroup:GetUnits()) do
if SpearheadGroup:GetCoalition() == 1 then
table.insert(blueUnitsPos, unit:getPoint())
elseif SpearheadGroup:GetCoalition() == 2 then
table.insert(redUnitsPos, unit:getPoint())
end
end
end
SpearheadGroup:Destroy()
end
--Cleanup units
local cleanup_distance = 5
for blueUnitName, blueUnitPos in pairs(blueUnitsPos) do
for redUnitName, redUnitPos in pairs(redUnitsPos) do
local distance = Spearhead.Util.VectorDistance3d(blueUnitPos, redUnitPos)
if distance <= cleanup_distance then
self._cleanupUnits[redUnitName] = true
end
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()
self._buildableMission = Spearhead.classes.stageClasses.missions.BuildableMission.new(database, logger, zoneName, noLandingZone, self._buildableCrateKilos, "SAM_CRATE")
self._buildableMission:AddOnCrateDroppedOfListener(self)
self._buildableMission:AddMissionCompleteListener(self)
end
return self
end
---@private
---@return SpearheadTriggerZone?
function BlueSam:GetNoLandingZone()
---@type Array<Vec2>
local points = {}
for _, group in pairs(self._blueGroups) 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
function BlueSam:Activate()
if self._buildableMission == nil then
self:SpawnGroups()
else
self._buildableMission:SpawnActive()
end
end
function BlueSam:SpawnGroups()
for unitName, needsCleanup in pairs(self._cleanupUnits) do
if needsCleanup == true then
Spearhead.DcsUtil.DestroyUnit(unitName)
@@ -86,6 +152,24 @@ function BlueSam:Activate()
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)
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
+176 -5
View File
@@ -1,12 +1,17 @@
---@class FarpZone
---@class FarpZone : OnCrateDroppedListener, MissionCompleteListener
---@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
@@ -36,22 +41,50 @@ function FarpZone.New(database, logger, zoneName)
local farpData = database:getFarpDataForZone(zoneName)
self._groups = {}
self._padNames = {}
self._supplyHubs = {}
if farpData then
self._padNames = farpData.padNames
for _, supplyHubName in pairs(farpData.supplyHubNames) do
local supplyHub = Spearhead.classes.stageClasses.SpecialZones.SupplyHub.new(database, logger, supplyHubName)
if supplyHub then
table.insert(self._supplyHubs, supplyHub)
end
end
for _, groupName in pairs(farpData.groups) do
local group = Spearhead.classes.stageClasses.Groups.SpearheadGroup.New(groupName)
table.insert(self._groups, group)
group:Destroy()
end
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()
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 totalGroups = Spearhead.Util.tableLength(self._groups)
self._groupsPerKilo = totalGroups / self._requiredBuildingKilos
end
end
if self._buildableMission == nil then
self._logger:debug("No buildable mission for zone: " .. zoneName)
end
self:Deactivate()
return self
end
---@return boolean
function FarpZone:IsStartingFarp()
return self._startingFarp
@@ -59,14 +92,112 @@ end
function FarpZone:Activate()
self._logger:info("Activating FARP zone: " .. self._zoneName)
self:BuildUp()
self:SetPadsBlue()
if self._buildableMission == nil then
self:BuildUp()
self:SetPadsBlue()
self:ActivateSupplyHubs()
else
self._buildableMission:SpawnActive()
end
end
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()
end
@@ -76,6 +207,13 @@ function FarpZone:BuildUp()
end
end
function FarpZone:ActivateSupplyHubs()
for _, supplyHub in pairs(self._supplyHubs) do
supplyHub:Activate()
end
end
---@private
function FarpZone:NeutralisePads()
for _, name in pairs(self._padNames) do
@@ -99,6 +237,39 @@ function FarpZone:SetPadsBlue()
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
+33 -22
View File
@@ -8,13 +8,14 @@
---@field private _cleanup_units table<string, boolean>
---@field private _airbase Airbase?
---@field private _initialSide number?
---@field private _supplyHubs Array<SupplyHub>
local StageBase = {}
---comment
---@param databaseManager Database
---@param logger table
---@param airbaseName string
---@return StageBase
---@return StageBase?
function StageBase.New(databaseManager, logger, airbaseName)
StageBase.__index = StageBase
@@ -26,45 +27,51 @@ function StageBase.New(databaseManager, logger, airbaseName)
self._red_groups = {}
self._blue_groups = {}
self._cleanup_units = {}
self._supplyHubs = {}
self._airbase = Airbase.getByName(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 = {}
---@type table<string, Vec3>
local blueUnitsPos = {}
do -- fill tables
local redGroups = databaseManager:getRedGroupsAtAirbase(airbaseName)
if redGroups then
for _, groupName in pairs(redGroups) do
local shGroup = Spearhead.classes.stageClasses.Groups.SpearheadGroup.New(groupName)
table.insert(self._red_groups, shGroup)
for _, groupName in pairs(airbaseData.RedGroups) do
local shGroup = Spearhead.classes.stageClasses.Groups.SpearheadGroup.New(groupName)
table.insert(self._red_groups, shGroup)
for _, unit in pairs(shGroup:GetUnits()) do
redUnitsPos[unit:getName()] = unit:getPoint()
end
shGroup:Destroy()
end
for _, unit in pairs(shGroup:GetUnits()) do
redUnitsPos[unit:getName()] = unit:getPoint()
end
local blueGroups = databaseManager:getBlueGroupsAtAirbase(airbaseName)
if blueGroups then
for _, groupName in pairs(blueGroups) do
local shGroup = Spearhead.classes.stageClasses.Groups.SpearheadGroup.New(groupName)
table.insert(self._blue_groups, shGroup)
shGroup:Destroy()
end
for _, unit in pairs(shGroup:GetUnits()) do
blueUnitsPos[unit:getName()] = unit:getPoint()
end
for _, groupName in pairs(airbaseData.BlueGroups) do
local shGroup = Spearhead.classes.stageClasses.Groups.SpearheadGroup.New(groupName)
table.insert(self._blue_groups, shGroup)
shGroup:Destroy()
for _, unit in pairs(shGroup:GetUnits()) do
blueUnitsPos[unit:getName()] = unit:getPoint()
end
shGroup:Destroy()
end
for _, supplyHubName in pairs(airbaseData.supplyHubNames) do
local supplyHub = Spearhead.classes.stageClasses.SpecialZones.SupplyHub.new(databaseManager, logger, supplyHubName)
if supplyHub then
table.insert(self._supplyHubs, supplyHub)
end
end
@@ -151,6 +158,10 @@ function StageBase:ActivateBlueStage()
self:CleanRedUnits()
self:SpawnBlueUnits()
for _, hub in pairs(self._supplyHubs) do
hub:Activate()
end
end
@@ -0,0 +1,88 @@
---@class SupplyHub
---@field private _database Database
---@field private _logger Logger
---@field private _zoneName string
---@field private _zone SpearheadTriggerZone?
---@field private _supplyUnitsTracker SupplyUnitsTracker
---@field private _isCommmandAdded table<string, boolean>
---@field private _missionCommandsHelper MissionCommandsHelper
---@field private _inZone table<string, boolean>
---@field private _drawID number
---@field private _cargoInUnits table<table<string, number>>
---@field private _activeAtStart boolean
---@field private _active boolean
local SupplyHub = {}
---@param database Database
---@param logger Logger
---@param zoneName string
---@return SupplyHub?
function SupplyHub.new(database, logger, zoneName)
SupplyHub.__index = SupplyHub
local self = setmetatable({}, SupplyHub)
self._database = database
self._logger = logger
self._zoneName = zoneName
local split = Spearhead.Util.split_string(zoneName, "_")
if string.lower(split[2]) == "a" then
self._activeAtStart = true
else
self._activeAtStart = false
end
self._zone = Spearhead.DcsUtil.getZoneByName(zoneName)
self._supplyUnitsTracker = Spearhead.classes.stageClasses.helpers.SupplyUnitsTracker.getOrCreate(logger.LogLevel)
self._inZone = {}
self._missionCommandsHelper = Spearhead.classes.stageClasses.helpers.MissionCommandsHelper.getOrCreate(logger.LogLevel)
self._logger:debug("Creating Supply Hub zone: " .. self._zoneName)
return self
end
function SupplyHub:IsActiveFromStart()
return self._activeAtStart
end
function SupplyHub:GetZoneName()
return self._zoneName
end
---@return SpearheadTriggerZone?
function SupplyHub:GetZone()
return self._zone
end
function SupplyHub:Activate()
if self._active == true then
return
end
self._active = true
self._logger:debug("Activating Supply Hub zone: " .. self._zoneName)
local zone = Spearhead.DcsUtil.getZoneByName(self._zoneName)
if zone and self._drawID == nil then
---@type DrawColor
local fillColor = { r=0, g=1, b=0, a=0.2 }
---@type DrawColor
local lineColor = { r=0, g=1, b=0, a=1}
local lineStyle = 1
self._drawID = Spearhead.DcsUtil.DrawZone(zone, lineColor, fillColor, lineStyle)
end
self._supplyUnitsTracker:RegisterHub(self)
end
if not Spearhead 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.SupplyHub = SupplyHub
@@ -14,6 +14,7 @@
--- @field miscGroups Array<SpearheadGroup>
--- @field maxMissions integer
--- @field farps Array<FarpZone>
--- @field supplyHubs Array<SupplyHub>
--- @class StageInitData
--- @field stageZoneName string
@@ -90,7 +91,9 @@ function Stage:superNew(database, stageConfig, logger, initData, missionPriority
maxMissions = stageConfig.maxMissionsPerStage,
farps = {},
missionsByName = {},
supplyHubs = {}
}
self._activeStage = -99
self._preActivated = false
self._stageConfig = stageConfig or {}
@@ -110,6 +113,11 @@ function Stage:superNew(database, stageConfig, logger, initData, missionPriority
table.insert(self._db.farps, farp)
end
local supplyHubNames = database:getSupplyHubsInStage(self.zoneName)
for _, supplyHubName in pairs(supplyHubNames) do
local supplyHub = Spearhead.classes.stageClasses.SpecialZones.SupplyHub.new(database, logger, supplyHubName)
table.insert(self._db.supplyHubs, supplyHub)
end
self._logger:info("Initiating new Stage with name: " .. self.zoneName)
@@ -404,6 +412,12 @@ function Stage:ActivateStage()
end
end
for _, supplyHub in pairs(self._db.supplyHubs) do
if supplyHub:IsActiveFromStart() == true then
supplyHub:Activate()
end
end
timer.scheduleFunction(self.CheckContinuousAsync, self, timer.getTime() + 3)
end
@@ -476,6 +490,10 @@ function Stage:ActivateBlueGroups()
farp:Activate()
end
end
for _, supplyHub in pairs(self._db.supplyHubs) do
supplyHub:Activate()
end
end
---@return number strike
+1 -1
View File
@@ -18,7 +18,7 @@ function PrimaryStage.New(database, stageConfig, logger, initData)
local self = setmetatable({}, { __index = PrimaryStage }) --[[@as PrimaryStage]]
self:superNew(database, stageConfig, logger, initData, "primary")
return self
end
if not Spearhead.classes then Spearhead.classes = {} end
@@ -5,10 +5,13 @@
---@field lastUpdate number @last update time
---@field updateContinuous fun(self: MissionCommandsHelper, time: number): number @function to update commands continuously
---@field pinnedByGroup table<string, Mission> @table of pinned missions by group ID
---@field private _supplyHubGroups table<string, boolean> @table of supply hub groups by their ID
---@field private _logger Logger @logger instance for logging
---@field private _supplyUnitsTracker SupplyUnitsTracker @supply units tracker instance
local MissionCommandsHelper = {}
MissionCommandsHelper.__index = MissionCommandsHelper
local id = 0
local instance = nil
@@ -16,21 +19,25 @@ local instance = nil
---@param logLevel string @log level for the logger
function MissionCommandsHelper.getOrCreate(logLevel)
if instance == nil then
local self = setmetatable({}, MissionCommandsHelper)
instance = setmetatable({}, MissionCommandsHelper)
self._logger = Spearhead.LoggerTemplate.new("MissionCommandsHelper", logLevel)
instance._logger = Spearhead.LoggerTemplate.new("MissionCommandsHelper", logLevel)
self.missionsByCode = {}
self.enabledByCode = {}
self.updateNeeded = false
self.pinnedByGroup = {}
self.lastUpdate = 0
instance._logger:info("Creating MissionCommandsHelper instance")
instance.missionsByCode = {}
instance.enabledByCode = {}
instance.updateNeeded = false
instance.pinnedByGroup = {}
instance.lastUpdate = 0
instance._supplyHubGroups = {}
instance._supplyUnitsTracker = Spearhead.classes.stageClasses.helpers.SupplyUnitsTracker.getOrCreate(logLevel)
---comment
---@param selfA MissionCommandsHelper
---@param time number
---@return number
self.updateContinuous = function(selfA, time)
instance.updateContinuous = function(selfA, time)
if selfA.updateNeeded == false then
return time + 10
end
@@ -49,14 +56,63 @@ function MissionCommandsHelper.getOrCreate(logLevel)
return time + 10
end
timer.scheduleFunction(self.updateContinuous, self, timer.getTime() + 5)
Spearhead.Events.AddOnPlayerEnterUnitListener(self)
instance = self
timer.scheduleFunction(instance.updateContinuous, instance, timer.getTime() + 5)
Spearhead.Events.AddOnPlayerEnterUnitListener(instance)
end
return instance
end
---@param mission Mission
function MissionCommandsHelper:AddMissionToCommands(mission)
self._logger:debug("Adding mission to commands: [" .. mission.code .. "]" .. mission.name)
self.missionsByCode[tostring(mission.code)] = mission
self.enabledByCode[tostring(mission.code)] = true
self.updateNeeded = true
end
---Removes a mission from the F10 commands menu
---@param mission Mission
function MissionCommandsHelper:RemoveMissionToCommands(mission)
self.enabledByCode[tostring(mission.code)] = false
self.updateNeeded = true
end
---@param groupID number
function MissionCommandsHelper:MarkUnitInSupplyHub(groupID)
self._logger:debug("Marking unit in supply hub: " .. tostring(groupID))
local updateNeeded = false
if self._supplyHubGroups[tostring(groupID)] ~= true then
updateNeeded = true
end
self._supplyHubGroups[tostring(groupID)] = true
if updateNeeded == true then self:updateCommandsForGroup(groupID) end
end
---@param groupID number
function MissionCommandsHelper:MarkUnitOutsideSupplyHub(groupID)
self._logger:debug("Marking unit outide supply hub: " .. tostring(groupID))
local updateNeeded = false
if self._supplyHubGroups[tostring(groupID)] == true then
updateNeeded = true
end
self._supplyHubGroups[tostring(groupID)] = false
if updateNeeded == true then self:updateCommandsForGroup(groupID) end
end
---@param unit Unit
function MissionCommandsHelper:OnPlayerEntersUnit(unit)
if unit then
local group = unit:getGroup()
if group then self:updateCommandsForGroup(group:getID()) end
end
end
---@class MissionBriefingRequestedArgs
---@field mission Mission @the mission object
@@ -72,6 +128,22 @@ local missionBriefingRequested = function(args)
mission:ShowBriefing(groupID)
end
---@class MarkRequestedArgs
---@field mission Mission @the mission object
---@field groupId integer @the group ID of the player requesting the briefing
---@param args MarkRequestedArgs
local markRequested = function(args)
local mission = args.mission
if not mission then return end
if mission.missionType == "LOGISTICS" then
mission:MarkMissionAreaToGroup(args.groupId)
end
end
---@class PinMissionCommandArgs
---@field self MissionCommandsHelper @the MissionCommandsHelper instance
@@ -114,7 +186,7 @@ function MissionCommandsHelper:AddOverviewCommand(groupID)
end
end
return string.format("[%s] %-15s %-20s %10s nM\n", mission.code, mission.missionTypeDisplay, mission.name, distanceText)
return string.format("[%s]\t%s \t%s \t%s nM\n", mission.code, mission.missionTypeDisplay, mission.name, distanceText)
end
---Primary missions
@@ -161,11 +233,11 @@ function MissionCommandsHelper:AddPinnedMission(groupID)
end
---comment
---@param groupID number
function MissionCommandsHelper:updateCommandsForGroup(groupID)
self._logger:debug("Updating commands for group: " .. tostring(groupID))
self:AddPinnedMission(groupID)
self:AddOverviewCommand(groupID)
@@ -180,6 +252,9 @@ function MissionCommandsHelper:updateCommandsForGroup(groupID)
end
end
self:AddSupplyHubCommandsIfApplicable(groupID)
self:AddCargoCommands(groupID)
---@param id number
local clearView = function(id)
trigger.action.outTextForGroup(id, "clearing...", 1, true)
@@ -192,7 +267,9 @@ end
local folderNames = {
primary = "Primary Missions",
secondary = "Secondary Missions"
secondary = "Secondary Missions",
supplyHub = "Supply Hub",
cargo = "Cargo"
}
@@ -221,31 +298,135 @@ function MissionCommandsHelper:addMissionCommands(groupId, mission)
local missionFolderName = "[" .. mission.code .. "]" .. mission.name
missionCommands.addSubMenuForGroup(groupId, missionFolderName, path)
table.insert(path, missionFolderName)
missionCommands.addCommandForGroup(groupId, "Briefing", path, missionBriefingRequested,{ groupId = groupId, mission = mission })
missionCommands.addCommandForGroup(groupId, "Pin", path, pinMissionCommand, { self = self, groupId = groupId, mission = mission })
---@type MissionBriefingRequestedArgs
local missionBriefingRequestedArgs = { groupId = groupId, mission = mission }
missionCommands.addCommandForGroup(groupId, "Briefing", path, missionBriefingRequested,missionBriefingRequestedArgs)
---@type PinMissionCommandArgs
local pinMissionCommandArgs = { self = self, groupId = groupId, mission = mission }
missionCommands.addCommandForGroup(groupId, "Pin", path, pinMissionCommand, pinMissionCommandArgs)
if mission.missionType == "LOGISTICS" then
---@type MarkRequestedArgs
local markRequestArgs = { groupId = groupId, mission = mission }
missionCommands.addCommandForGroup(groupId, "Mark", path, markRequested, markRequestArgs)
end
end
end
---@param mission Mission
function MissionCommandsHelper:AddMissionToCommands(mission)
self._logger:debug("Adding mission to commands: [" .. mission.code .. "]" .. mission.name)
self.missionsByCode[tostring(mission.code)] = mission
self.enabledByCode[tostring(mission.code)] = true
self.updateNeeded = true
---@private
---@param groupID integer
function MissionCommandsHelper:AddSupplyHubCommandsIfApplicable(groupID)
if self._supplyHubGroups[tostring(groupID)] ~= true then return end
self._logger:debug("Adding supply hub commands for group: " .. tostring(groupID))
local group = Spearhead.DcsUtil.GetPlayerGroupByGroupID(groupID)
if group == nil then return end
local unit = group:getUnit(1)
if unit == nil then return end
---@class LoadCargoCommandParams
---@field unitID number
---@field groupID number
---@field crateType CrateType
---@field supplyUnitsTracker SupplyUnitsTracker
---comment
---@param params LoadCargoCommandParams
local loadCargoCommand = function(params)
local crateType = params.crateType
local supplyUnitsTracker = params.supplyUnitsTracker
if supplyUnitsTracker then
supplyUnitsTracker:UnitRequestCrateLoading(params.groupID, crateType)
end
end
local path = { [1] = folderNames.supplyHub }
---@type LoadCargoCommandParams
local farpParams1000 = { unitID = unit:getID(), groupID = group:getID(), crateType = "FARP_CRATE_1000", supplyUnitsTracker = self._supplyUnitsTracker }
missionCommands.addCommandForGroup(groupID, "Load FARP Crate (1000)", path, loadCargoCommand, farpParams1000)
---@type LoadCargoCommandParams
local farpParams2000 = { unitID = unit:getID(), groupID = group:getID(), crateType = "FARP_CRATE_2000", supplyUnitsTracker = self._supplyUnitsTracker }
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 }
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 }
missionCommands.addCommandForGroup(groupID, "Load SAM Crate (2000)", path, loadCargoCommand, samParms2000)
end
---Removes a mission from the F10 commands menu
---@param mission Mission
function MissionCommandsHelper:RemoveMissionToCommands(mission)
self.enabledByCode[tostring(mission.code)] = false
self.updateNeeded = true
function MissionCommandsHelper:AddCargoCommands(groupID)
local group = Spearhead.DcsUtil.GetPlayerGroupByGroupID(groupID)
if group == nil then return end
local unit = group:getUnit(1)
if unit == nil then return end
---@class UnloadCargoCommandParams
---@field unitID number
---@field crateType CrateType
---@field supplyUnitsTracker SupplyUnitsTracker
---comment
---@param params UnloadCargoCommandParams
local unloadCargoCommand = function(params)
local unitID = params.unitID
local crateType = params.crateType
params.supplyUnitsTracker:UnloadRequested(unitID, crateType)
end
local cargo = self._supplyUnitsTracker:GetCargoInUnit(unit:getID())
if cargo then
for cargoType, amount in pairs(cargo) do
local cargoConfig = Spearhead.classes.stageClasses.helpers.supplies.SupplyConfigHelper.getSupplyConfig(cargoType)
if cargoConfig then
for i = 1, amount do
local path = { [1] = folderNames.cargo }
---@type UnloadCargoCommandParams
local params = { unitID = unit:getID(), crateType = cargoType, supplyUnitsTracker = self._supplyUnitsTracker }
missionCommands.addCommandForGroup(groupID, "Unload " .. cargoConfig.displayName, path, unloadCargoCommand, params)
end
end
end
end
end
---@private
---@param groupId integer
function MissionCommandsHelper:addMissionFolders(groupId)
missionCommands.addSubMenuForGroup(groupId, folderNames.primary)
missionCommands.addSubMenuForGroup(groupId, folderNames.secondary)
if self._supplyHubGroups[tostring(groupId)] == true then
self._logger:debug("Adding supply hub commands folder for group: " .. tostring(groupId))
missionCommands.addSubMenuForGroup(groupId, folderNames.supplyHub)
end
local group = Spearhead.DcsUtil.GetPlayerGroupByGroupID(groupId)
if group == nil then return end
local unit = group:getUnit(1)
if unit == nil then return end
local cargo = self._supplyUnitsTracker:GetCargoInUnit(unit:getID())
if cargo ~= nil then
missionCommands.addSubMenuForGroup(groupId, folderNames.cargo)
end
end
---@private
@@ -253,6 +434,8 @@ end
function MissionCommandsHelper:removeMissionFolders(groupId)
missionCommands.removeItemForGroup(groupId, { folderNames.primary })
missionCommands.removeItemForGroup(groupId, { folderNames.secondary })
missionCommands.removeItemForGroup(groupId, { folderNames.supplyHub })
missionCommands.removeItemForGroup(groupId, { folderNames.cargo })
end
---@private
@@ -264,15 +447,6 @@ function MissionCommandsHelper:ResetFolders(groupID)
self:addMissionFolders(groupID)
end
---comment
---@param unit Unit
function MissionCommandsHelper:OnPlayerEntersUnit(unit)
if unit then
local group = unit:getGroup()
if group then self:updateCommandsForGroup(group:getID()) 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
@@ -0,0 +1,106 @@
---@alias SupplyType
---| "FARP_CRATE"
---| "SAM_CRATE"
---@alias CrateType
---| "FARP_CRATE_500"
---| "FARP_CRATE_1000"
---| "FARP_CRATE_2000"
---| "SAM_CRATE_500"
---| "SAM_CRATE_1000"
---| "SAM_CRATE_2000"
---@class SupplyConfig
---@field type SupplyType
---@field weight number
---@field staticType string
---@field displayName string
---@type table<CrateType, SupplyConfig>
local SupplyConfig = {
["FARP_CRATE_500"] = {
type = "FARP_CRATE",
weight = 500,
displayName = "FARP Crate (500)",
staticType = "container_cargo",
},
["FARP_CRATE_1000"] = {
type = "FARP_CRATE",
weight = 1000,
displayName = "FARP Crate (1000)",
staticType = "container_cargo",
},
["FARP_CRATE_2000"] = {
type = "FARP_CRATE",
weight = 2000,
displayName = "FARP Crate (2000)",
staticType = "container_cargo",
},
["SAM_CRATE_500"] = {
type = "SAM_CRATE",
weight = 1000,
displayName = "SAM Crate (500)",
staticType = "container_cargo",
},
["SAM_CRATE_1000"] = {
type = "SAM_CRATE",
weight = 1000,
displayName = "SAM Crate (1000)",
staticType = "container_cargo",
},
["SAM_CRATE_2000"] = {
type = "SAM_CRATE",
weight = 2000,
displayName = "SAM Crate (2000)",
staticType = "container_cargo",
},
}
---@class MaxLoadConfig
---@field maxInternalLoad number
---@type table<string, MaxLoadConfig>
MaxLoadConfig = {
["Mi-8MT"] = {
maxInternalLoad = 4000,
},
["CH-47Fbl1"] = {
maxInternalLoad = 10000
},
["Mi-24P"] = {
maxInternalLoad = 2000
},
["UH-1H"] = {
maxInternalLoad = 2000
}
}
---@class SupplyConfigHelper
local SupplyConfigHelper = {}
---comment
---@param name string
---@return SupplyConfig?
function SupplyConfigHelper.fromObjectName(name)
for configName, config in pairs(SupplyConfig) do
if Spearhead.Util.startswith(name, configName, true) == true then
return config
end
end
return nil
end
---@param type CrateType
function SupplyConfigHelper.getSupplyConfig(type)
return SupplyConfig[type]
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.helpers == nil then Spearhead.classes.stageClasses.helpers = {} end
if Spearhead.classes.stageClasses.helpers.supplies == nil then Spearhead.classes.stageClasses.helpers.supplies = {} end
Spearhead.classes.stageClasses.helpers.supplies.MaxLoadConfig = MaxLoadConfig
Spearhead.classes.stageClasses.helpers.supplies.SupplyConfigHelper = SupplyConfigHelper
@@ -0,0 +1,407 @@
---@class SupplyUnitsTracker
---@field private _supplyUnitsByName table<string, Unit>
---@field private _cargoInUnits table<string, table<CrateType, number>>
---@field private _logger Logger
---@field private _unitPositions table<string, Vec3>
---@field private _commandsHelper MissionCommandsHelper
---@field private _droppedCrates table<string, StaticObject>
---@field private _registeredHubs table<SupplyHub, boolean>
local SupplyUnitsTracker = {}
SupplyUnitsTracker.__index = SupplyUnitsTracker
-- A single class tracking all units is more than enough.
local singleton = nil
---comment
---@param logLevel LogLevel
---@return SupplyUnitsTracker
function SupplyUnitsTracker.getOrCreate(logLevel)
if singleton == nil then
singleton = setmetatable({}, SupplyUnitsTracker)
singleton._logger = Spearhead.LoggerTemplate.new("SupplyUnitsTracker", logLevel)
singleton._unitPositions = {}
singleton._cargoInUnits = {}
singleton._supplyUnitsByName = {}
singleton._droppedCrates = {}
singleton._registeredHubs = {}
singleton._commandsHelper = Spearhead.classes.stageClasses.helpers.MissionCommandsHelper.getOrCreate(singleton._logger.LogLevel)
Spearhead.Events.AddOnPlayerEnterUnitListener(singleton)
---@param selfA SupplyUnitsTracker
local function updateTask(selfA, time)
selfA:Update()
return time + 15
end
timer.scheduleFunction(updateTask, singleton, timer.getTime() + 15)
---comment
---@param selfA SupplyUnitsTracker
---@param time number
---@return number
local function checkUnitsInZone(selfA, time)
pcall(function()
selfA:CheckUnitsInZones()
end)
return time + 5
end
timer.scheduleFunction(checkUnitsInZone, singleton, timer.getTime() + 5)
end
return singleton
end
---@param unit Unit
function SupplyUnitsTracker:OnPlayerEntersUnit(unit)
if unit == nil then return end
if self:IsSupplyUnit(unit) == true then
self._supplyUnitsByName[unit:getName()] = unit
end
end
function SupplyUnitsTracker:Update()
local players = Spearhead.DcsUtil.getAllPlayerUnits()
for _, player in pairs(players) do
if player ~= nil and player:isExist() and self:IsSupplyUnit(player) == true then
self._supplyUnitsByName[player:getName()] = player
end
end
end
---@private
function SupplyUnitsTracker:IsSupplyUnit(unit)
if unit == nil then return false end
if unit:hasAttribute("Transport helicopters") then
return true
end
return false
end
---comment
---@param unitID number
---@param crateType CrateType
function SupplyUnitsTracker:AddCargoToUnit(unitID, crateType)
if unitID == nil or crateType == nil then return end
local unit = Spearhead.DcsUtil.GetPLayerUnitByID(unitID)
if unit == nil then return end
if self._cargoInUnits[unitID] == nil then
self._cargoInUnits[unitID] = {}
end
if self._cargoInUnits[unitID][crateType] == nil then
self._cargoInUnits[unitID][crateType] = 0
end
self._cargoInUnits[unitID][crateType] = self._cargoInUnits[unitID][crateType] + 1
end
---@param unitID number
---@param crateType CrateType
function SupplyUnitsTracker:RemoveCargoFromUnit(unitID, crateType)
if unitID == nil or crateType == nil then return end
local unitIDStr = tostring(unitID)
if self._cargoInUnits[unitIDStr] == nil then return end
if self._cargoInUnits[unitIDStr][crateType] == nil then return end
self._cargoInUnits[unitIDStr][crateType] = self._cargoInUnits[unitIDStr][crateType] - 1
local hasCargo = false
for type, count in pairs(self._cargoInUnits[unitIDStr]) do
if count > 0 then
hasCargo = true
break
end
end
if hasCargo == false then
self._cargoInUnits[unitIDStr] = nil
end
end
function SupplyUnitsTracker:CheckUnitsInZones()
for name, unit in pairs(self._supplyUnitsByName) do
if unit ~= nil and unit:isExist() == true then
self._logger:debug("Checking unit: " .. unit:getName())
local pos = unit:getPoint()
local group = unit:getGroup()
for hub, enabled in pairs(self._registeredHubs) do
if enabled == true then
local zone = hub:GetZone()
if zone ~= nil then
if Spearhead.Util.is3dPointInZone(pos, zone) then
self._commandsHelper:MarkUnitInSupplyHub(group:getID())
else
self._commandsHelper:MarkUnitOutsideSupplyHub(group:getID())
end
end
end
end
self._unitPositions[unit:getID()] = pos
end
end
end
function SupplyUnitsTracker:RegisterHub(hub)
if hub == nil then return end
if self._registeredHubs[hub] == nil then
self._registeredHubs[hub] = true
end
end
---@param unitID number
---@return table<CrateType, number>?
function SupplyUnitsTracker:GetCargoInUnit(unitID)
if unitID == nil then return end
local unitIDStr = tostring(unitID)
if self._cargoInUnits[unitIDStr] == nil then return end
return self._cargoInUnits[unitIDStr]
end
---@return table<string, Unit>
function SupplyUnitsTracker:GetUnits()
return self._supplyUnitsByName
end
local cargoCount = 0
function SupplyUnitsTracker:UnloadRequested(unitID, crateType)
self._logger:debug("Unload requested for unit: " .. unitID .. " crateType: " .. crateType)
local unit = Spearhead.DcsUtil.GetPLayerUnitByID(unitID)
if unit == nil or unit:isExist() == false then return end
local group = unit:getGroup()
if group == nil then
return
end
self:RemoveCargoFromUnit(unitID, crateType)
local cargoConfig = Spearhead.classes.stageClasses.helpers.supplies.SupplyConfigHelper.getSupplyConfig(crateType)
if cargoConfig == nil then
self._logger:error("Invalid crate type: " .. crateType)
return
end
local cargoPos = self:GetCargoPlacePosition(unit)
cargoCount = cargoCount + 1
local cargoSpawnObject = {
name = crateType .. "_" .. cargoCount,
type = cargoConfig.staticType,
x = cargoPos.x,
y = cargoPos.z,
}
local spawned = coalition.addStaticObject(unit:getCoalition(), cargoSpawnObject)
self._droppedCrates[cargoSpawnObject.name] = spawned
self._commandsHelper:updateCommandsForGroup(group:getID())
end
---@return table<string,StaticObject>
function SupplyUnitsTracker:GetCargoCratesDropped()
return self._droppedCrates
end
---Loads a crate directly into the unit
---@param groupID number
---@param crateType CrateType
function SupplyUnitsTracker:UnitRequestCrateLoading(groupID, crateType)
self._logger:debug("UnitRequestCrateLoading called with groupID: " .. groupID .. " and crateType: " .. crateType)
local group = Spearhead.DcsUtil.GetPlayerGroupByGroupID(groupID)
if group ~= nil then
local crateConfig = Spearhead.classes.stageClasses.helpers.supplies.SupplyConfigHelper.getSupplyConfig(crateType)
if crateConfig == nil then
self._logger:error("Invalid crate type: " .. crateType)
return
end
local unit = group:getUnit(1)
if unit == nil then return end
if unit:isExist() == false then return end
if unit:inAir() == true then
trigger.action.outTextForUnit(unit:getID(), "Land first before crates can be loaded", 10)
return
end
trigger.action.outTextForUnit(unit:getID(), "Loading crate of type " .. crateType, 13)
---@class LoadCargoParams
---@field self SupplyUnitsTracker
---@field unit Unit
---@field groupID number
---@field crateType CrateType
---@param params LoadCargoParams
local LoadCrateTask = function(params)
local loaded = params.self:TryLoadCrateInUnit(params.unit, params.crateType)
if loaded ~= false then
trigger.action.outTextForUnit(unit:getID(), "Loaded crate :" .. params.crateType, 10)
end
end
---@type LoadCargoParams
local params = {
self = self,
unit = unit,
crateType = crateType,
groupID = groupID
}
timer.scheduleFunction(LoadCrateTask, params, timer.getTime() + 15)
end
end
---comment
---@param unit Unit
---@param crateType CrateType
---@return boolean
function SupplyUnitsTracker:TryLoadCrateInUnit(unit, crateType)
local crateConfigA = Spearhead.classes.stageClasses.helpers.supplies.SupplyConfigHelper.getSupplyConfig(crateType)
if crateConfigA == nil then
trigger.action.outText("Invalid crate type: " .. crateType, 5)
return false
end
local currentWeight = 0
for _, cargo in pairs(self._cargoInUnits) do
if cargo[crateType] ~= nil then
currentWeight = currentWeight + (cargo[crateType] * crateConfigA.weight)
end
end
local unitConfig = Spearhead.classes.stageClasses.helpers.supplies.MaxLoadConfig[unit:getTypeName()]
if unitConfig == nil then
trigger.action.outText("Your unit type is not configured for logistics: " .. crateType, 5)
self._logger:error("Invalid unit type: " .. unit:getTypeName())
return false
end
local maxWeight = unitConfig.maxInternalLoad
if currentWeight + crateConfigA.weight > maxWeight then
trigger.action.outText("Failed to load crate due to it overloading your max weight of: " .. maxWeight .. "kg", 5)
return false
end
trigger.action.setUnitInternalCargo(unit:getName(), crateConfigA.weight)
self:AddCargoToUnit(unit:getID(), crateType)
local group = unit:getGroup()
if group == nil then return false end
local groupID = group:getID()
self._commandsHelper:updateCommandsForGroup(groupID)
return true
end
---Spawns a crate for sling loading
---@param groupID number
---@param crateType CrateType
function SupplyUnitsTracker:UnitRequestCrateSpawn(groupID, crateType)
local group = Spearhead.DcsUtil.GetPlayerGroupByGroupID(groupID)
if group == nil then
local crateConfig = Spearhead.classes.stageClasses.helpers.supplies.SupplyConfigHelper.getSupplyConfig(crateType)
if crateConfig == nil then
self._logger:error("Invalid crate type: " .. crateType)
return
end
end
end
---@private
---@param unit Unit
---@return Vec3
function SupplyUnitsTracker:GetCargoPlacePosition(unit)
local pos = unit:getPosition()
local preferedPos = {
x = pos.p.x - 10 * pos.x.x,
y = pos.p.y - 10 * pos.x.y,
z = pos.p.z - 10 * pos.x.z
}
return preferedPos
-- local volume = {
-- id = world.VolumeType.SPHERE,
-- params = {
-- point = preferedPos,
-- radius = 10
-- }
-- }
-- local occupiedPosX = {}
-- local occupiedPosZ = {}
-- ---@param foundItem Object
-- local found = function(foundItem, val)
-- local foundPos = foundItem:getPoint()
-- local z = math.floor(foundPos.z)
-- for i = z - 3 , z + 3 do
-- occupiedPosZ[i] = true
-- end
-- local x = math.floor(foundPos.x)
-- for i = x - 3 , x + 3 do
-- occupiedPosX[i] = true
-- end
-- end
-- world.searchObjects(volume.id, volume.params, found)
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.helpers == nil then Spearhead.classes.stageClasses.helpers = {} end
Spearhead.classes.stageClasses.helpers.SupplyUnitsTracker = SupplyUnitsTracker
@@ -0,0 +1,229 @@
---@class BuildableMission : Mission
---@field private _requiredKilos number
---@field private _droppedKilos number
---@field private _crateType SupplyType
---@field private _targetZoneName string
---@field private _database Database
---@field private _onCrateDroppedOfListeners Array<OnCrateDroppedListener>
---@field private _markIDsPerGroup table<string, number>
---@field private _supplyUnitsTracker SupplyUnitsTracker
---@field private _noLandingZone SpearheadTriggerZone?
---@field private _dropOffZone SpearheadTriggerZone?
---@field private _noLandingZoneId number
---@field private _dropOffZoneId number
local BuildableMission = {}
BuildableMission.__index = BuildableMission
---@class OnCrateDroppedListener
---@field OnCrateDroppedOff fun(self:OnCrateDroppedListener, mission:BuildableMission, kilos:number)
---@param database Database
---@param targetZoneName string
---@param requiredKilos number
---@param requiredCrateType SupplyType
---@param noLandingZone SpearheadTriggerZone?
---@param logger Logger
function BuildableMission.new(database, logger, targetZoneName, noLandingZone, requiredKilos, requiredCrateType)
local Mission = Spearhead.classes.stageClasses.missions.baseMissions.Mission
setmetatable(BuildableMission, Mission)
local self = setmetatable({}, { __index = BuildableMission })
self._targetZoneName = targetZoneName
self._database = database
self._requiredKilos = requiredKilos
self._droppedKilos = 0
self._noLandingZone = noLandingZone
if noLandingZone then
local verts = noLandingZone.verts
local enlarged = Spearhead.Util.enlargeConvexHull(verts, 300)
---@type SpearheadTriggerZone
local dropOfZone = {
name = targetZoneName .. "_dropZone",
zone_type = "Polygon",
radius = 0,
verts = enlarged,
location = noLandingZone.location,
}
self._dropOffZone = dropOfZone
end
self.code = tostring(database:GetNewMissionCode())
self.name = "Resupply"
local type = "site"
if requiredCrateType == "SAM_CRATE" then
type = "SAM site"
elseif requiredCrateType == "FARP_CRATE" then
type = "FARP"
end
self.zoneName = targetZoneName .. "_supply"
self._logger = logger
self._onCrateDroppedOfListeners = {}
self._completeListeners = {}
self._markIDsPerGroup = {}
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.missionType = "LOGISTICS"
self.missionTypeDisplay = "LOGISTICS"
self.priority = "secondary"
self._missionCommandsHelper = Spearhead.classes.stageClasses.helpers.MissionCommandsHelper.getOrCreate(self._logger.LogLevel)
self._crateType = requiredCrateType
return self
end
---@param listener OnCrateDroppedListener
function BuildableMission:AddOnCrateDroppedOfListener(listener)
table.insert(self._onCrateDroppedOfListeners, listener)
end
function BuildableMission:ShowBriefing(groupID)
local group = Spearhead.DcsUtil.GetPlayerGroupByGroupID(groupID)
if group == nil then return end
local unitType = Spearhead.DcsUtil.getUnitTypeFromGroup(group)
local coords = Spearhead.DcsUtil.convertVec2ToUnitUsableType(self.location, unitType)
local siteType = "FARP"
if self._crateType == "SAM_CRATE" then
siteType = "SAM site"
end
local briefing = "Mission [" .. self.code .. "] " .. self.name ..
"\n \n" ..
"We've dispatched forward units to find a proper spot for a new " .. siteType .. "." ..
"\nYou will need to drop off supplies so they can start building." ..
"\nThe coords are: " .. coords ..
"\n\n" ..
"\nKilos still required: " .. self._requiredKilos - self._droppedKilos ..
"\n\n" ..
"NOTE: Do not land in the orange construction zone!"
trigger.action.outTextForGroup(groupID, briefing, 30)
end
function BuildableMission:MarkMissionAreaToGroup(groupID)
local text = "[" .. self.code .. "] " .. self.name
local location = { x= self.location.x, y=land.getHeight(self.location), z=self.location.y }
local markID = Spearhead.DcsUtil.AddMarkToGroup(groupID, text, location)
self._markIDsPerGroup[groupID] = markID
end
---@private
---@param crate SupplyConfig
function BuildableMission:NotifyCrateDroppedOf(crate)
for _, listener in ipairs(self._onCrateDroppedOfListeners) do
if listener.OnCrateDroppedOff then
listener:OnCrateDroppedOff(self, crate.weight)
end
end
end
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)
return
end
---@type DrawColor
local lineColor = { r=230/255, g=93/255, b=49/255, a=1}
---@type DrawColor
local fillColor = { r=230/255, g=93/255, b=49/255, a=0.2}
self._noLandingZoneId = Spearhead.DcsUtil.DrawZone(self._noLandingZone, lineColor, fillColor, 6)
if self._dropOffZone == nil then
self._logger:error("No drop off zone found for mission: " .. self.code)
return
end
local lineColor2 = { r=0, g=0, b=1, a=1}
local fillColor2 = { r=0, g=0, b=1, a=0}
self._dropOffZoneId = Spearhead.DcsUtil.DrawZone(self._dropOffZone, lineColor2, fillColor2, 6)
---@param selfA BuildableMission
---@param time number
local checkForCrateTasks = function (selfA, time)
selfA:CheckCratesInZone()
return time + 10
end
timer.scheduleFunction(checkForCrateTasks, self, timer.getTime() + 10)
self:SpawnForwardUnits()
self._state = "ACTIVE"
self._missionCommandsHelper:AddMissionToCommands(self)
end
function BuildableMission:SpawnForwardUnits()
end
function BuildableMission:CheckCratesInZone()
---@type Array<Object>
local foundCrates = {}
local crates = self._supplyUnitsTracker:GetCargoCratesDropped()
for _, staticObject in pairs(crates) do
if staticObject and staticObject:isExist() and Spearhead.Util.startswith(staticObject:getName(), self._crateType, true) then
local pos = staticObject:getPoint()
if Spearhead.Util.is3dPointInZone(pos, self._dropOffZone) then
table.insert(foundCrates, staticObject)
end
end
end
for _, foundCrate in pairs(foundCrates) do
local crateConfig = Spearhead.classes.stageClasses.helpers.supplies.SupplyConfigHelper.fromObjectName(foundCrate:getName())
if crateConfig then
self._droppedKilos = self._droppedKilos + crateConfig.weight
foundCrate:destroy()
self:NotifyCrateDroppedOf(crateConfig)
end
end
if self._droppedKilos >= self._requiredKilos then
Spearhead.DcsUtil.RemoveMark(self._noLandingZoneId)
Spearhead.DcsUtil.RemoveMark(self._dropOffZoneId)
self:NotifyMissionComplete()
self._state = "COMPLETED"
end
end
if not Spearhead.classes then Spearhead.classes = {} end
if not Spearhead.classes.stageClasses then Spearhead.classes.stageClasses = {} end
if not Spearhead.classes.stageClasses.missions then Spearhead.classes.stageClasses.missions = {} end
Spearhead.classes.stageClasses.missions.BuildableMission = BuildableMission
@@ -1,5 +1,6 @@
---@class Mission
---@field name string
---@field zoneName string
@@ -120,6 +121,8 @@ function Mission:NotifyMissionComplete()
end
function Mission:MarkMissionAreaToGroup(groupId) end
---endregion
--region PROTECTED
@@ -155,6 +158,7 @@ do --aliases
--- | "DEAD"
--- | "SAM"
--- | "OCA"
--- | "LOGISTICS"
--- @alias MissionState
--- | "NEW"
+4
View File
@@ -23,6 +23,7 @@ assert(loadfile(classPath .. "stageClasses\\GlobalStageManager.lua"))()
assert(loadfile(classPath .. "stageClasses\\missions\\baseMissions\\Mission.lua"))()
assert(loadfile(classPath .. "stageClasses\\missions\\ZoneMission.lua"))()
assert(loadfile(classPath .. "stageClasses\\missions\\RunwayStrikeMission.lua"))()
assert(loadfile(classPath .. "stageClasses\\missions\\BuildableMission.lua"))()
assert(loadfile(classPath .. "stageClasses\\Stages\\BaseStage\\Stage.lua"))()
@@ -34,10 +35,13 @@ assert(loadfile(classPath .. "stageClasses\\Stages\\WaitingStage.lua"))()
assert(loadfile(classPath .. "stageClasses\\Groups\\SpearheadGroup.lua"))()
assert(loadfile(classPath .. "stageClasses\\helpers\\MissionCommandsHelper.lua"))()
assert(loadfile(classPath .. "stageClasses\\helpers\\SupplyConfig.lua"))()
assert(loadfile(classPath .. "stageClasses\\helpers\\SupplyUnitsTracker.lua"))()
assert(loadfile(classPath .. "stageClasses\\SpecialZones\\StageBase.lua"))()
assert(loadfile(classPath .. "stageClasses\\SpecialZones\\BlueSam.lua"))()
assert(loadfile(classPath .. "stageClasses\\SpecialZones\\FarpZone.lua"))()
assert(loadfile(classPath .. "stageClasses\\SpecialZones\\SupplyHub.lua"))()
assert(loadfile(classPath .. "capClasses\\CapGroup.lua"))()
assert(loadfile(classPath .. "capClasses\\GlobalCapManager.lua"))()