From 1b0ad13529a4cb81502b3e25579c5e805e7a21e3 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 26 Aug 2021 21:24:47 +0200 Subject: [PATCH] OPS Commander --- .../Moose/Functional/Warehouse.lua | 32 +- Moose Development/Moose/Ops/Cohort.lua | 4 +- Moose Development/Moose/Ops/Commander.lua | 288 ++++++++++++++---- Moose Development/Moose/Ops/Legion.lua | 57 +++- Moose Development/Moose/Ops/OpsTransport.lua | 2 + 5 files changed, 296 insertions(+), 87 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 836e053cd..c664f4f4b 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -1622,13 +1622,17 @@ WAREHOUSE = { -- @field #boolean spawned If true, asset was spawned into the cruel world. If false, it is still in stock. -- @field #string spawngroupname Name of the spawned group. -- @field #boolean iscargo If true, asset is cargo. If false asset is transport. Nil if in stock. --- @field #number rid The request ID of this asset. -- @field #boolean arrived If true, asset arrived at its destination. +-- -- @field #number damage Damage of asset group in percent. -- @field Ops.AirWing#AIRWING.Payload payload The payload of the asset. --- @field Ops.FlightGroup#FLIGHTGROUP flightgroup The flightgroup object. +-- @field Ops.OpsGroup#OPSGROUP flightgroup The flightgroup object. +-- @field Ops.Cohort#COHORT cohort The cohort this asset belongs to. +-- @field Ops.Legion#LEGION legion The legion this asset belonts to. -- @field #string squadname Name of the squadron this asset belongs to. --- @field #number Treturned Time stamp when asset returned to the airwing. +-- @field #number Treturned Time stamp when asset returned to its legion (airwing, brigade). +-- @field #boolean requested If `true`, asset was requested and cannot be selected by another request. +-- @field #boolean isReserved If `true`, asset was reserved and cannot be selected by another request. --- Item of the warehouse queue table. -- @type WAREHOUSE.Queueitem @@ -3118,14 +3122,16 @@ end -- @param #WAREHOUSE self -- @return DCS#Vec3 The 3D vector of the warehouse. function WAREHOUSE:GetVec3() - return self.warehouse:GetVec3() + local vec3=self.warehouse:GetVec3() + return vec3 end --- Get 2D vector of warehouse static. -- @param #WAREHOUSE self -- @return DCS#Vec2 The 2D vector of the warehouse. function WAREHOUSE:GetVec2() - return self.warehouse:GetVec2() + local vec2=self.warehouse:GetVec2() + return vec2 end @@ -3194,18 +3200,6 @@ function WAREHOUSE:GetAssignment(request) return tostring(request.assignment) end ---[[ ---- Get warehouse unique ID from static warehouse object. This is the ID under which you find the @{#WAREHOUSE} object in the global data base. --- @param #WAREHOUSE self --- @param #string staticname Name of the warehouse static object. --- @return #number Warehouse unique ID. -function WAREHOUSE:GetWarehouseID(staticname) - local warehouse=STATIC:FindByName(staticname, true) - local uid=tonumber(warehouse:GetID()) - return uid -end -]] - --- Find a warehouse in the global warehouse data base. -- @param #WAREHOUSE self -- @param #number uid The unique ID of the warehouse. @@ -3951,6 +3945,8 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu -- Asset is not spawned. asset.spawned=false + asset.requested=false + asset.isReserved=false asset.iscargo=nil asset.arrived=nil @@ -4141,6 +4137,8 @@ function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute, forcecargobay, asset.skill=skill asset.assignment=assignment asset.spawned=false + asset.requested=false + asset.isReserved=false asset.life0=group:GetLife0() asset.damage=0 asset.spawngroupname=string.format("%s_AID-%d", templategroupname, asset.uid) diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index afc15c1ee..5847acd73 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -372,6 +372,8 @@ end function COHORT:AddAsset(Asset) self:T(self.lid..string.format("Adding asset %s of type %s", Asset.spawngroupname, Asset.unittype)) Asset.squadname=self.name + Asset.legion=self.legion + Asset.cohort=self table.insert(self.assets, Asset) return self end @@ -700,7 +702,7 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Check if there is a cohort that can execute a given mission. --- We check the mission type, the refuelling system, engagement range +-- We check the mission type, the refuelling system, mission range. -- @param #COHORT self -- @param Ops.Auftrag#AUFTRAG Mission The mission. -- @return #boolean If true, Cohort can do that type of mission. diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index 9c5781277..f973f8311 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -28,15 +28,13 @@ -- -- # The COMMANDER Concept -- --- A commander is the head of legions. He will find the best LEGIONs to perform an assigned AUFTRAG (mission). +-- A commander is the head of legions. He/she will find the best LEGIONs to perform an assigned AUFTRAG (mission). -- -- -- @field #COMMANDER COMMANDER = { ClassName = "COMMANDER", - Debug = nil, - lid = nil, - legions = {}, + legions = {}, missionqueue = {}, } @@ -50,6 +48,8 @@ COMMANDER.version="0.1.0" -- TODO: Improve legion selection. Mostly done! -- TODO: Allow multiple Legions for one mission. +-- TODO: Add ops transports. +-- TODO: Find solution for missions, which require a transport. This is not as easy as it sounds since the selected mission assets restrict the possible transport assets. -- NOGO: Maybe it's possible to preselect the assets for the mission. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -247,15 +247,19 @@ function COMMANDER:onafterStatus(From, Event, To) -- FSM state. local fsmstate=self:GetState() - -- Check mission queue and assign one PLANNED mission. - self:CheckMissionQueue() - -- Status. local text=string.format("Status %s: Legions=%d, Missions=%d", fsmstate, #self.legions, #self.missionqueue) self:I(self.lid..text) - - -- Legion info. + + -- Check mission queue and assign one PLANNED mission. + self:CheckMissionQueue() + + --- + -- LEGIONS + --- + if #self.legions>0 then + local text="Legions:" for _,_legion in pairs(self.legions) do local legion=_legion --Ops.Legion#LEGION @@ -272,8 +276,70 @@ function COMMANDER:onafterStatus(From, Event, To) end end self:I(self.lid..text) + + + local assets={} + + local Ntotal=0 + local Nspawned=0 + local Nrequested=0 + local Nreserved=0 + local Nstock=0 + + local text="===========================================\n" + text=text.."Assets:" + for _,_legion in pairs(self.legions) do + local legion=_legion --Ops.Legion#LEGION + + for _,_cohort in pairs(legion.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + + for _,_asset in pairs(cohort.assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + table.insert(assets, asset) + + text=text..string.format("\n- %s [UID=%d] Legion=%s, Cohort=%s: Spawned=%s, Requested=%s [RID=%s], Reserved=%s", + asset.spawngroupname, asset.uid, legion.alias, cohort.name, tostring(asset.spawned), tostring(asset.requested), tostring(asset.rid), tostring(asset.isReserved)) + + if asset.spawned then + Nspawned=Nspawned+1 + end + + if asset.requested then + Nrequested=Nrequested+1 + end + + if asset.isReserved then + Nreserved=Nreserved+1 + end + + if not (asset.spawned or asset.requested or asset.isReserved) then + Nstock=Nstock+1 + end + + Ntotal=Ntotal+1 + + end + + end + + end + text=text.."\n-------------------------------------------" + text=text..string.format("\nNstock = %d", Nstock) + text=text..string.format("\nNreserved = %d", Nreserved) + text=text..string.format("\nNrequested = %d", Nrequested) + text=text..string.format("\nNspawned = %d", Nspawned) + text=text..string.format("\nNtotal = %d (=%d)", Ntotal, Nstock+Nspawned+Nrequested+Nreserved) + text=text.."\n===========================================" + self:I(self.lid..text) + end + --- + -- MISSIONS + --- + -- Mission queue. if #self.missionqueue>0 then @@ -313,6 +379,9 @@ function COMMANDER:onafterMissionAssign(From, Event, To, Legion, Mission) -- Add mission to legion. Legion:AddMission(Mission) + + -- Directly request the mission as the assets have already been selected. + Legion:MissionRequest(Mission) end @@ -362,24 +431,126 @@ end function COMMANDER:CheckMissionQueue() -- TODO: Sort mission queue. wrt what? Threat level? + -- Currently, we sort wrt to priority. So that should reflect the threat level of the mission target. + -- Number of missions. + local Nmissions=#self.missionqueue + + -- Treat special cases. + if Nmissions==0 then + return nil + end + + -- Sort results table wrt prio and start time. + local function _sort(a, b) + local taskA=a --Ops.Auftrag#AUFTRAG + local taskB=b --Ops.Auftrag#AUFTRAG + return (taskA.prio=mission.nassets then + + for _,_asset in pairs(assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + asset.payload=asset.legion:FetchPayloadFromStock(asset.unittype, mission.type, mission.payloads) + asset.score=asset.legion:CalculateAssetMissionScore(asset, mission, true) + end + --- Sort assets wrt to their mission score. Higher is better. + local function optimize(assetA, assetB) + return (assetA.score>assetB.score) + end + table.sort(assets, optimize) + + -- Remove distance parameter. + local text=string.format("Optimized assets for %s mission:", mission.type) + for i,Asset in pairs(assets) do + local asset=Asset --Functional.Warehouse#WAREHOUSE.Assetitem + + -- Score text. + text=text..string.format("\n%s %s: score=%d", asset.squadname, asset.spawngroupname, asset.score) + + -- Nillify score. + asset.score=nil + + -- Add assets to mission. + if i<=mission.nassets then + + -- Add asset to mission. + mission:AddAsset(Asset) + + -- Put into table. + legions[asset.legion.alias]=asset.legion + + -- Number of assets requested from this legion. + -- TODO: Check if this is really necessary as we do not go through the selection process. + mission.Nassets=mission.Nassets or {} + if mission.Nassets[asset.legion.alias] then + mission.Nassets[asset.legion.alias]=mission.Nassets[asset.legion.alias]+1 + else + mission.Nassets[asset.legion.alias]=1 + end + + else + + -- Return payload of asset (if any). + if asset.payload then + asset.legion:ReturnPayloadFromAsset(asset) + end + + end + end + self:T2(self.lid..text) + + else + self:T2(self.lid..string.format("Not enough assets available for mission")) + end + + --- + -- Assign Mission to Legions + --- + if legions then for _,_legion in pairs(legions) do local legion=_legion --Ops.Legion#LEGION + + -- Debug message. + self:I(self.lid..string.format("Assigning mission %s [%s] to legion %s", mission:GetName(), mission:GetType(), legion.alias)) -- Add mission to legion. self:MissionAssign(legion, mission) @@ -424,7 +595,7 @@ function COMMANDER:GetLegionsForMission(Mission) end -- Has it assets that can? - if Nassets>0 then + if Nassets>0 and false then -- Get coordinate of the target. local coord=Mission:GetTargetCoordinate() @@ -446,62 +617,15 @@ function COMMANDER:GetLegionsForMission(Mission) end end + + -- Add legion if it can provide at least 1 asset. + if Nassets>0 then + table.insert(legions, legion) + end end - -- Can anyone? - if #legions>0 then - - --- Something like: - -- * Closest legion that can should be first prio. - -- * However, there should be a certain "quantization". if wing is 50 or 60 NM way should not really matter. In that case, the legion with more resources should get the job. - local function score(a) - local d=math.round(a.dist/10) - end - - env.info(self.lid.."FF #legions="..#legions) - - -- Sort table wrt distance and number of assets. - -- Distances within 10 NM are equal and the legion with more assets is preferred. - local function sortdist(a,b) - local ad=a.dist - local bd=b.dist - return adb.nassets) - end - table.sort(legions, sortdist) - - - -- Loops over all legions and stop if enough assets are summed up. - local selection={} ; local N=0 - for _,leg in ipairs(legions) do - local legion=leg.airwing --Ops.Legion#LEGION - - Mission.Nassets=Mission.Nassets or {} - Mission.Nassets[legion.alias]=leg.nassets - - table.insert(selection, legion) - - N=N+leg.nassets - - if N>=Mission.nassets then - self:I(self.lid..string.format("Found enough assets!")) - break - end - end - - if N>=Mission.nassets then - self:I(self.lid..string.format("Found %d legions that can do mission %s (%s) requiring %d assets", #selection, Mission:GetName(), Mission:GetType(), Mission.nassets)) - return selection - else - self:T(self.lid..string.format("Not enough LEGIONs found that could do the job :/ Number of assets avail %d < %d required for the mission", N, Mission.nassets)) - return nil - end - - else - self:T(self.lid..string.format("No LEGION found that could do the job :/")) - end - - return nil + return legions end --- Count assets of all assigned legions. @@ -521,6 +645,42 @@ function COMMANDER:CountAssets(InStock, MissionTypes, Attributes) return N end +--- Count assets of all assigned legions. +-- @param #COMMANDER self +-- @param #boolean InStock If true, only assets that are in the warehouse stock/inventory are counted. +-- @param #table Legions (Optional) Table of legions. Default is all legions. +-- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types. +-- @param #table Attributes (Optional) Count only assest that have a certain attribute(s), e.g. `WAREHOUSE.Attribute.AIR_BOMBER`. +-- @return #number Amount of asset groups. +function COMMANDER:GetAssets(InStock, Legions, MissionTypes, Attributes) + + -- Selected assets. + local assets={} + + for _,_legion in pairs(Legions or self.legions) do + local legion=_legion --Ops.Legion#LEGION + + --TODO Check if legion is running and maybe if runway is operational if air assets are requested. + + for _,_cohort in pairs(legion.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + + for _,_asset in pairs(cohort.assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + -- TODO: Check if repaired. + -- TODO: currently we take only unspawned assets. + if not (asset.spawned or asset.isReserved or asset.requested) then + table.insert(assets, asset) + end + + end + end + end + + return assets +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 762800ccf..1c8cb5802 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -118,6 +118,12 @@ function LEGION:New(WarehouseName, LegionName) -- @param #LEGION self -- @param Ops.Auftrag#AUFTRAG Mission The mission. + --- Triggers the FSM event "MissionCancel" after a delay. + -- @function [parent=#LEGION] __MissionCancel + -- @param #LEGION self + -- @param #number delay Delay in seconds. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + --- On after "MissionCancel" event. -- @function [parent=#LEGION] OnAfterMissionCancel -- @param #LEGION self @@ -446,12 +452,14 @@ function LEGION:_GetNextTransport() local function getAssets(n) local assets={} - -- Loop over assets. + -- Loop over cohorts. for _,_cohort in pairs(self.cohorts) do local cohort=_cohort --Ops.Cohort#COHORT + -- Check if chort can do a transport. if cohort:CheckMissionCapability({AUFTRAG.Type.OPSTRANSPORT}, cohort.missiontypes) then + -- Loop over cohort assets. for _,_asset in pairs(cohort.assets) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem @@ -533,6 +541,23 @@ function LEGION:CalculateAssetMissionScore(asset, Mission, includePayload) score=score+25 end end + + -- Get coordinate of the target. + local coord=Mission:GetTargetCoordinate() + local dist=0 + if coord then + + -- Distance from legion to target. + local distance=UTILS.MetersToNM(coord:Get2DDistance(self:GetCoordinate())) + + -- Round: 55 NM ==> 5.5 ==> 6, 63 NM ==> 6.3 ==> 6 + dist=UTILS.Round(distance/10, 0) + + end + + -- Reduce score for legions that are futher away. + score=score-dist + -- TODO: This could be vastly improved. Need to gather ideas during testing. -- Calculate ETA? Assets on orbit missions should arrive faster even if they are further away. @@ -669,6 +694,7 @@ function LEGION:onafterMissionRequest(From, Event, To, Mission) -- Set asset to requested! Important so that new requests do not use this asset! asset.requested=true + asset.isReserved=false if Mission.missionTask then asset.missionTask=Mission.missionTask @@ -728,6 +754,7 @@ function LEGION:onafterTransportRequest(From, Event, To, OpsTransport) -- The queueid has been increased in the onafterAddRequest function. So we can simply use it here. OpsTransport.requestID=OpsTransport.requestID or {} OpsTransport.requestID[self.alias]=self.queueid + end end @@ -1174,7 +1201,6 @@ function LEGION:_CreateFlightGroup(asset) opsgroup=FLIGHTGROUP:New(asset.spawngroupname) - elseif self:IsBrigade() then --- @@ -1183,8 +1209,6 @@ function LEGION:_CreateFlightGroup(asset) opsgroup=ARMYGROUP:New(asset.spawngroupname) - - else self:E(self.lid.."ERROR: not airwing or brigade!") end @@ -1198,6 +1222,8 @@ function LEGION:_CreateFlightGroup(asset) -- Set home base. opsgroup.homebase=self.airbase + -- Set home zone. + opsgroup.homezone=self.spawnzone -- Set weapon data. if opsgroup.cohort.weaponData then @@ -1594,10 +1620,11 @@ function LEGION:CanMission(Mission) end end + -- Loop over cohorts and recruit assets. for cohortname,_cohort in pairs(cohorts) do local cohort=_cohort --Ops.Cohort#COHORT - -- Check if this squadron can. + -- Check if this cohort can. local can=cohort:CanMission(Mission) if can then @@ -1774,6 +1801,26 @@ function LEGION:GetMissionFromRequest(Request) return self:GetMissionFromRequestID(Request.uid) end +--- Fetch a payload from the airwing resources for a given unit and mission type. +-- The payload with the highest priority is preferred. +-- @param #LEGION self +-- @param #string UnitType The type of the unit. +-- @param #string MissionType The mission type. +-- @param #table Payloads Specific payloads only to be considered. +-- @return Ops.Airwing#AIRWING.Payload Payload table or *nil*. +function LEGION:FetchPayloadFromStock(UnitType, MissionType, Payloads) + -- Polymorphic. Will return something when called by airwing. + return nil +end + +--- Return payload from asset back to stock. +-- @param #LEGION self +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The squadron asset. +function LEGION:ReturnPayloadFromAsset(asset) + -- Polymorphic. + return nil +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index d82192cae..0dde8433c 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -52,6 +52,8 @@ -- @field #number Ndelivered Total number of cargo groups delivered. -- @field #table pathsTransport Transport paths of `#OPSGROUP.Path`. -- @field #table pathsPickup Pickup paths of `#OPSGROUP.Path`. +-- @field Ops.Auftrag#AUFTRAG mission The mission attached to this transport. +-- @field #table assets Warehouse assets assigned for this transport. -- @extends Core.Fsm#FSM --- *Victory is the beautiful, bright-colored flower. Transport is the stem without which it could never have blossomed.* -- Winston Churchill