diff --git a/DcsTypes.lua b/DcsTypes.lua index d09f2b1..1bf8544 100644 --- a/DcsTypes.lua +++ b/DcsTypes.lua @@ -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: { [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.
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. diff --git a/_docs/pages/reference.html b/_docs/pages/reference.html index 3a2d817..fda5751 100644 --- a/_docs/pages/reference.html +++ b/_docs/pages/reference.html @@ -78,6 +78,8 @@
  • Randomization
  • +
  • In stage dependencies
  • +
  • Buildables
  • Runway Bombing
  • @@ -164,6 +166,22 @@

    + +

    Supply Hub

    +

    + Format: SUPPLYHUB_[Name]
    + Format: SUPPLYHUB_A_[Name]
    + Example: SUPPLYHUB_HOUSTON

    + + Supply hubs are used as zones to pick up logistics for logistic missions.
    + They are activated when their parent is activated.
    + Activated depends on what it's parent is.
    + 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.
    + If you place the Supply Hub trigger zone inside of a FARP zone the supply hub will activate when the FARP zone is activated.
    + If the FARP needs to be built first, then that means the supply hub also is activated later.
    + + NOTE: The supply hub does not spawn anything, but merely give options to pick up logistics. +

    Mission Types

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

    +

    Buildables

    +

    + Current buildables:
    + FARP_
    + BLUESAM_
    +
    + + Buildables are zones or objects that first require logistics.
    + Eg. A forward SAM site or FARP requires logistic crates before it can be built.
    + + To make an item buildable add a text box inside of the trigger zone and name it: buildable_[freeform]
    + Then in the text box type amount of kilo's you want to be transfered before the logistisc mission is complete.
    + 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.
    + Right now a crate takes 15 seconds to unpack per 500kg. Meaning 2 crates of 1000kg will be faster than 1 crate of 2000kg.
    + + All helicopters have a max limit set based on quick google searches and cannot be changed but the ME at this point:
    + UH-1H : 2000kg
    + Mi-8 : 4000kg
    + CH-47 : 10000kg
    + MI-24 : 2000kg
    +

    +

    Runway Bombing

    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.
    diff --git a/_miz/SPEARHEADDEV.miz b/_miz/SPEARHEADDEV.miz index 093f1d7..7d45243 100644 Binary files a/_miz/SPEARHEADDEV.miz and b/_miz/SPEARHEADDEV.miz differ diff --git a/classes/capClasses/CapAirbase.lua b/classes/capClasses/CapAirbase.lua index 25273fa..29741fb 100644 --- a/classes/capClasses/CapAirbase.lua +++ b/classes/capClasses/CapAirbase.lua @@ -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 = {} diff --git a/classes/definitions/aliases.lua b/classes/definitions/aliases.lua index 5459251..bffd2df 100644 --- a/classes/definitions/aliases.lua +++ b/classes/definitions/aliases.lua @@ -12,10 +12,5 @@ do -- mission aliases ---@class Array: { [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 diff --git a/classes/persistence/Persistence.lua b/classes/persistence/Persistence.lua index 851f2dc..5efba98 100644 --- a/classes/persistence/Persistence.lua +++ b/classes/persistence/Persistence.lua @@ -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 diff --git a/classes/spearhead_base.lua b/classes/spearhead_base.lua index 7ae0884..76ab771 100644 --- a/classes/spearhead_base.lua +++ b/classes/spearhead_base.lua @@ -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 points ---@return Array 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 diff --git a/classes/spearhead_db.lua b/classes/spearhead_db.lua index baf5279..10b423f 100644 --- a/classes/spearhead_db.lua +++ b/classes/spearhead_db.lua @@ -11,6 +11,7 @@ ---@field CapDataPerStageNumber table table ---@field CarrierRouteZones Array All Carrier routes zones ---@field BlueSams Array All blue sam zones +---@field SupplyHubZones Array All supply hub zones ---@field MissionAnnotations table table ---@field AirbaseDataPerAirfield table ---@field BlueSamDataPerZone table @@ -39,15 +40,19 @@ ---@field RandomMissionZones Array ---@field StageIndex string ---@field BlueSamZones Array +---@field SupplyHubZones Array +---@field SupplyHubZonesInFarp table ---@field MiscGroups Array ---@class AirbaseData ---@field CapGroups Array ---@field RedGroups Array ---@field BlueGroups Array +---@field supplyHubNames Array ---@class BlueSamData ----@field Groups Array +---@field groups Array +---@field buildingKilos number? ---@class MissionZoneData ---@field Groups Array @@ -58,6 +63,8 @@ ---@class FarpZoneData ---@field groups Array ---@field padNames Array +---@field buildingKilos number? +---@field supplyHubNames Array ---@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 + 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 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 -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 function Database:getBlueSamsInStage(stageName) @@ -796,29 +907,33 @@ function Database:getBlueSamsInStage(stageName) return stageData.BlueSamZones end +---@param stageName string +---@return Array +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 -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 -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 -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] diff --git a/classes/stageClasses/Groups/SpearheadGroup.lua b/classes/stageClasses/Groups/SpearheadGroup.lua index ee5defd..a915c74 100644 --- a/classes/stageClasses/Groups/SpearheadGroup.lua +++ b/classes/stageClasses/Groups/SpearheadGroup.lua @@ -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 +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 diff --git a/classes/stageClasses/SpecialZones/BlueSam.lua b/classes/stageClasses/SpecialZones/BlueSam.lua index c3b8ad8..00e9850 100644 --- a/classes/stageClasses/SpecialZones/BlueSam.lua +++ b/classes/stageClasses/SpecialZones/BlueSam.lua @@ -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 ---@field private _cleanupUnits table +---@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 - local blueUnitsPos = {} + if blueSamData == nil then + logger:error("Blue SAM data not found for zone: " .. zoneName) + return nil + end - ---@type table - 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 + local blueUnitsPos = {} + ---@type table + 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 + 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 diff --git a/classes/stageClasses/SpecialZones/FarpZone.lua b/classes/stageClasses/SpecialZones/FarpZone.lua index ac6d716..456ac11 100644 --- a/classes/stageClasses/SpecialZones/FarpZone.lua +++ b/classes/stageClasses/SpecialZones/FarpZone.lua @@ -1,12 +1,17 @@ ----@class FarpZone +---@class FarpZone : OnCrateDroppedListener, MissionCompleteListener ---@field private _startingFarp boolean ---@field private _groups Array ---@field private _padNames Array ---@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 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 + 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 diff --git a/classes/stageClasses/SpecialZones/StageBase.lua b/classes/stageClasses/SpecialZones/StageBase.lua index cb4e126..4589665 100644 --- a/classes/stageClasses/SpecialZones/StageBase.lua +++ b/classes/stageClasses/SpecialZones/StageBase.lua @@ -8,13 +8,14 @@ ---@field private _cleanup_units table ---@field private _airbase Airbase? ---@field private _initialSide number? +---@field private _supplyHubs Array 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 local redUnitsPos = {} ---@type table 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 diff --git a/classes/stageClasses/SpecialZones/SupplyHub.lua b/classes/stageClasses/SpecialZones/SupplyHub.lua new file mode 100644 index 0000000..8d578b7 --- /dev/null +++ b/classes/stageClasses/SpecialZones/SupplyHub.lua @@ -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 +---@field private _missionCommandsHelper MissionCommandsHelper +---@field private _inZone table +---@field private _drawID number +---@field private _cargoInUnits table> +---@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 \ No newline at end of file diff --git a/classes/stageClasses/Stages/BaseStage/Stage.lua b/classes/stageClasses/Stages/BaseStage/Stage.lua index 4ac05cb..b9c2303 100644 --- a/classes/stageClasses/Stages/BaseStage/Stage.lua +++ b/classes/stageClasses/Stages/BaseStage/Stage.lua @@ -14,6 +14,7 @@ --- @field miscGroups Array --- @field maxMissions integer --- @field farps Array +--- @field supplyHubs Array --- @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 diff --git a/classes/stageClasses/Stages/PrimaryStage.lua b/classes/stageClasses/Stages/PrimaryStage.lua index c8681e7..c25c82e 100644 --- a/classes/stageClasses/Stages/PrimaryStage.lua +++ b/classes/stageClasses/Stages/PrimaryStage.lua @@ -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 diff --git a/classes/stageClasses/helpers/MissionCommandsHelper.lua b/classes/stageClasses/helpers/MissionCommandsHelper.lua index dbc1d78..6b03b4b 100644 --- a/classes/stageClasses/helpers/MissionCommandsHelper.lua +++ b/classes/stageClasses/helpers/MissionCommandsHelper.lua @@ -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 @table of pinned missions by group ID +---@field private _supplyHubGroups table @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 diff --git a/classes/stageClasses/helpers/SupplyConfig.lua b/classes/stageClasses/helpers/SupplyConfig.lua new file mode 100644 index 0000000..97908a1 --- /dev/null +++ b/classes/stageClasses/helpers/SupplyConfig.lua @@ -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 +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 +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 + diff --git a/classes/stageClasses/helpers/SupplyUnitsTracker.lua b/classes/stageClasses/helpers/SupplyUnitsTracker.lua new file mode 100644 index 0000000..4374429 --- /dev/null +++ b/classes/stageClasses/helpers/SupplyUnitsTracker.lua @@ -0,0 +1,407 @@ +---@class SupplyUnitsTracker +---@field private _supplyUnitsByName table +---@field private _cargoInUnits table> +---@field private _logger Logger +---@field private _unitPositions table +---@field private _commandsHelper MissionCommandsHelper +---@field private _droppedCrates table +---@field private _registeredHubs table +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? +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 +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 +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 \ No newline at end of file diff --git a/classes/stageClasses/missions/BuildableMission.lua b/classes/stageClasses/missions/BuildableMission.lua new file mode 100644 index 0000000..a5deb86 --- /dev/null +++ b/classes/stageClasses/missions/BuildableMission.lua @@ -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 +---@field private _markIDsPerGroup table +---@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 + 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 \ No newline at end of file diff --git a/classes/stageClasses/missions/baseMissions/Mission.lua b/classes/stageClasses/missions/baseMissions/Mission.lua index 26c8708..eaeac3e 100644 --- a/classes/stageClasses/missions/baseMissions/Mission.lua +++ b/classes/stageClasses/missions/baseMissions/Mission.lua @@ -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" diff --git a/dev/dev_classes.lua b/dev/dev_classes.lua index 068c986..865408b 100644 --- a/dev/dev_classes.lua +++ b/dev/dev_classes.lua @@ -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"))()