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
@@ -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"