From 884c51a69a34839f6b5d129765e71cf11469c15f Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 10 Sep 2021 00:32:15 +0200 Subject: [PATCH] OPSTRANSPORT - Improved assignment to multiple legions. --- Moose Development/Moose/Ops/Auftrag.lua | 48 ++++- Moose Development/Moose/Ops/Brigade.lua | 16 ++ Moose Development/Moose/Ops/Legion.lua | 94 +++++---- Moose Development/Moose/Ops/OpsTransport.lua | 191 +++++++++++++++++-- 4 files changed, 290 insertions(+), 59 deletions(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 8e68cb931..d45c51332 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -34,7 +34,7 @@ -- @field #string type Mission type. -- @field #string status Mission status. -- @field #table legions Assigned legions. --- @field #table statusLegion Mission status of all assigned LEGIONSs. +-- @field #table statusLegion Mission status of all assigned LEGIONs. -- @field #string statusCommander Mission status of the COMMANDER. -- @field #string statusChief Mission status of the CHIF. -- @field #table groupdata Group specific data. @@ -538,19 +538,22 @@ function AUFTRAG:New(Type) -- State is planned. self.status=AUFTRAG.Status.PLANNED - -- Defaults + -- Defaults . self:SetName() self:SetPriority() self:SetTime() + self:SetRequiredAssets() + self:SetRequiredCarriers() self.engageAsGroup=true + self.dTevaluate=5 + + -- Init counters and stuff. self.repeated=0 self.repeatedSuccess=0 self.repeatedFailure=0 self.Nrepeat=0 self.NrepeatFailure=0 self.NrepeatSuccess=0 - self.nassets=1 - self.dTevaluate=5 self.Ncasualties=0 self.Nkills=0 self.Nelements=0 @@ -1854,10 +1857,38 @@ function AUFTRAG:SetTransportForAssets(DeployZone, DisembarkZone, Carriers) end + -- Set min/max number of carriers to be assigned. + self.opstransport.nCarriersMin=self.nCarriersMin + self.opstransport.nCarriersMax=self.nCarriersMax + return self end ---- Add a transport Legion. This requires an OPSTRANSPORT to be set via `AUFTRAG:SetTransportForAssets`. +--- Set number of required carrier groups if an OPSTRANSPORT assignment is required. +-- @param #AUFTRAG self +-- @param #number NcarriersMin Number of carriers *at least* required. Default 1. +-- @param #number NcarriersMax Number of carriers *at most* used for transportation. Default is same as `NcarriersMin`. +-- @return #AUFTRAG self +function AUFTRAG:SetRequiredCarriers(NcarriersMin, NcarriersMax) + + self.nCarriersMin=NcarriersMin or 1 + + self.nCarriersMax=NcarriersMax or self.nCarriersMin + + -- Ensure that max is at least equal to min. + if self.nCarriersMax=2 then + local text=string.format("Transports Total=%d:", #self.transportqueue) + for i,_transport in pairs(self.transportqueue) do + local transport=_transport --Ops.OpsTransport#OPSTRANSPORT + + local prio=string.format("%d/%s", transport.prio, tostring(transport.importance)) ; if transport.urgent then prio=prio.." (!)" end + local carriers=string.format("Ncargo=%d/%d, Ncarriers=%d", transport.Ncargo, transport.Ndelivered, transport.Ncarrier) + + text=text..string.format("\n[%d] UID=%d: Status=%s, Prio=%s, Cargo: %s", i, transport.uid, transport:GetState(), prio, carriers) + end + self:I(self.lid..text) + end + ------------------- -- Platoon Info -- ------------------- diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 32344af38..84988cce6 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -256,7 +256,6 @@ function LEGION:AddMission(Mission) -- Add ops transport to transport Legions. if Mission.opstransport then - -- Add a new TZC: from pickup here to the deploy zone. local tzc=Mission.opstransport:AddTransportZoneCombo(self.spawnzone, Mission.opstransport.tzcDefault.DeployZone) @@ -315,9 +314,15 @@ function LEGION:AddOpsTransport(OpsTransport) -- Is not queued at a legion. OpsTransport:Queued() + + -- Set legion status. + OpsTransport:SetLegionStatus(self, AUFTRAG.Status.QUEUED) -- Add mission to queue. table.insert(self.transportqueue, OpsTransport) + + -- Add this legion to the transport. + OpsTransport:AddLegion(self) -- Info text. local text=string.format("Added Transport %s. Starting at %s-%s", @@ -427,7 +432,7 @@ function LEGION:_GetNextMission() end table.sort(self.missionqueue, _sort) - -- Look for first mission that is SCHEDULED. + -- Search min importance. local vip=math.huge for _,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG @@ -436,9 +441,6 @@ function LEGION:_GetNextMission() end end - -- Current time. - local time=timer.getAbsTime() - -- Look for first task that is not accomplished. for _,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG @@ -453,7 +455,6 @@ function LEGION:_GetNextMission() return mission end - end -- mission due? end -- mission loop @@ -474,7 +475,7 @@ function LEGION:_GetNextTransport() end --- Function to get carrier assets from all cohorts. - local function getAssets(n, weightGroup) + local function getAssets(n, N , weightGroup) -- Selected assets. local assets={} @@ -496,7 +497,12 @@ function LEGION:_GetNextTransport() -- Add to assets. table.insert(assets, asset) - if #assets==n then + --TODO: Optimize Asset Selection! + + --TODO: Check if deploy and (any) pickup zone is an airbase, so airplanes can be used. + + -- Max number of assets reached. + if #assets==N then return assets end end @@ -506,16 +512,22 @@ function LEGION:_GetNextTransport() end end - return nil + -- At least min number reached? + if #assets>=n then + return assets + else + return nil + end end - + + --TODO: Sort transports wrt to prio and importance. See mission sorting! -- Look for first task that is not accomplished. for _,_transport in pairs(self.transportqueue) do local transport=_transport --Ops.OpsTransport#OPSTRANSPORT -- Check if transport is still queued and ready. - if transport:IsQueued() and transport:IsReadyToGo() then + if transport:IsQueued(self) and transport:IsReadyToGo() then -- Get all undelivered cargo ops groups. local cargoOpsGroups=transport:GetCargoOpsGroups(false) @@ -534,10 +546,14 @@ function LEGION:_GetNextTransport() end -- Get assets. If not enough assets can be found, nil is returned. - local assets=getAssets(1, weightGroup) + local assets=getAssets(transport.nCarriersMin, transport.nCarriersMax, weightGroup) if assets then - transport.assets=assets + for _,_asset in pairs(assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + asset.isReserved=true + transport:AddAsset(asset) + end return transport end @@ -547,7 +563,7 @@ function LEGION:_GetNextTransport() end - + -- No transport found. return nil end @@ -735,6 +751,7 @@ function LEGION:onafterMissionRequest(From, Event, To, Mission) asset.requested=true asset.isReserved=false + -- Set missin task so that the group is spawned with the right one. if Mission.missionTask then asset.missionTask=Mission.missionTask end @@ -760,38 +777,46 @@ end -- @param #string To To state. -- @param Ops.OpsTransport#OPSTRANSPORT Opstransport The requested mission. function LEGION:onafterTransportRequest(From, Event, To, OpsTransport) - - -- Set mission status from QUEUED to REQUESTED. - OpsTransport:Requested() - -- Set legion status. Ensures that it is not considered in the next selection. - --Mission:SetLegionStatus(self, AUFTRAG.Status.REQUESTED) + -- List of assets that will be requested. + local AssetList={} + + --TODO: Find spawned assets on ALERT 5 mission OPSTRANSPORT. - -- Add request to legion warehouse. - if #OpsTransport.assets>0 then - - --local text=string.format("Requesting assets for mission %s:", Mission.name) - for i,_asset in pairs(OpsTransport.assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + --local text=string.format("Requesting assets for mission %s:", Mission.name) + for i,_asset in pairs(OpsTransport.assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + -- Check that this asset belongs to this Legion warehouse. + if asset.wid==self.uid then -- Set asset to requested! Important so that new requests do not use this asset! asset.requested=true + asset.isReserved=false + + -- Set transport mission task. + asset.missionTask=ENUMS.MissionTask.TRANSPORT - -- Check max required transports. - if i==1 then - break - end - + -- Add asset to list. + table.insert(AssetList, asset) end + end + + if #AssetList>0 then + + -- Set mission status from QUEUED to REQUESTED. + OpsTransport:Requested() + + -- Set legion status. Ensures that it is not considered in the next selection. + OpsTransport:SetLegionStatus(self, OPSTRANSPORT.Status.REQUESTED) -- TODO: Get/set functions for assignment string. local assignment=string.format("Transport-%d", OpsTransport.uid) -- Add request to legion warehouse. - self:AddRequest(self, WAREHOUSE.Descriptor.ASSETLIST, OpsTransport.assets, #OpsTransport.assets, nil, nil, OpsTransport.prio, assignment) + self:AddRequest(self, WAREHOUSE.Descriptor.ASSETLIST, AssetList, #AssetList, nil, nil, OpsTransport.prio, assignment) -- 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 @@ -1619,11 +1644,14 @@ function LEGION:GetAircraftTypes(onlyactive, cohorts) end --- Check if assets for a given mission type are available. +-- +-- OBSOLETE and renamed to _CanMission (to see if it is still used somewhere) +-- -- @param #LEGION self -- @param Ops.Auftrag#AUFTRAG Mission The mission. -- @return #boolean If true, enough assets are available. -- @return #table Assets that can do the required mission. -function LEGION:CanMission(Mission) +function LEGION:_CanMission(Mission) -- Assume we CAN and NO assets are available. local Can=true diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index a77c7c4e9..50b613719 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -2,10 +2,10 @@ -- -- ## Main Features: -- --- * Transport troops from A to B. +-- * Transport troops from A to B -- * Supports ground, naval and airborne (airplanes and helicopters) units as carriers --- * Use combined forces (ground, naval, air) to transport the troops. --- * Additional FSM events to hook into and customize your mission design. +-- * Use combined forces (ground, naval, air) to transport the troops +-- * Additional FSM events to hook into and customize your mission design -- -- === -- @@ -51,6 +51,9 @@ -- -- @field Ops.Auftrag#AUFTRAG mission The mission attached to this transport. -- @field #table assets Warehouse assets assigned for this transport. +-- @field #table legions Assigned legions. +-- @field #table statusLegion Transport status of all assigned LEGIONs. +-- @field #table requestID The ID of the queued warehouse request. Necessary to cancel the request if the transport was cancelled before the request is processed. -- -- @extends Core.Fsm#FSM @@ -58,8 +61,6 @@ -- -- === -- --- ![Banner Image](..\Presentations\OPS\Transport\_Main.png) --- -- # The OPSTRANSPORT Concept -- -- This class simulates troop transport using carriers such as APCs, ships, helicopters or airplanes. The carriers and transported groups need to be OPSGROUPS (see ARMYGROUP, NAVYGROUP and FLIGHTGROUP classes). @@ -119,6 +120,9 @@ OPSTRANSPORT = { tzcCounter = 0, conditionStart = {}, assets = {}, + legions = {}, + statusLegion = {}, + requestID = {}, } --- Cargo transport status. @@ -209,16 +213,17 @@ function OPSTRANSPORT:New(CargoGroups, PickupZone, DeployZone) self.uid=_OPSTRANSPORTID -- Defaults. - self.cargos={} - self.carriers={} + self:SetPriority() + self:SetTime() + self:SetRequiredCarriers() + -- Init arrays and counters. + self.cargos={} + self.carriers={} self.Ncargo=0 self.Ncarrier=0 self.Ndelivered=0 - self:SetPriority() - self:SetTime() - -- Set default TZC. self.tzcDefault=self:AddTransportZoneCombo(PickupZone, DeployZone, CargoGroups) @@ -772,6 +777,33 @@ function OPSTRANSPORT:GetRequiredCargos(TransportZoneCombo) return TransportZoneCombo.RequiredCargos end +--- Set number of required carrier groups for an OPSTRANSPORT assignment. Only used if transport is assigned at **LEGION** or higher level. +-- @param #OPSTRANSPORT self +-- @param #number NcarriersMin Number of carriers *at least* required. Default 1. +-- @param #number NcarriersMax Number of carriers *at most* used for transportation. Default is same as `NcarriersMin`. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:SetRequiredCarriers(NcarriersMin, NcarriersMax) + + self.nCarriersMin=NcarriersMin or 1 + + self.nCarriersMax=NcarriersMax or self.nCarriersMin + + -- Ensure that max is at least equal to min. + if self.nCarriersMax%s", Legion.alias, tostring(status), tostring(Status))) + + -- New status. + self.statusLegion[Legion.alias]=Status + + return self +end + +--- Get LEGION transport status. +-- @param #OPSTRANSPORT self +-- @param Ops.Legion#LEGION Legion The legion. +-- @return #string status Current status. +function OPSTRANSPORT:GetLegionStatus(Legion) + + -- Current status. + local status=self.statusLegion[Legion.alias] or "unknown" + + return status +end + --- Check if state is PLANNED. -- @param #OPSTRANSPORT self -- @return #boolean If true, status is PLANNED. function OPSTRANSPORT:IsPlanned() - return self:is(OPSTRANSPORT.Status.PLANNED) + local is=self:is(OPSTRANSPORT.Status.PLANNED) + return is end --- Check if state is QUEUED. -- @param #OPSTRANSPORT self +-- @param Ops.Legion#LEGION Legion (Optional) Check if transport is queued at this legion. -- @return #boolean If true, status is QUEUED. -function OPSTRANSPORT:IsQueued() - return self:is(OPSTRANSPORT.Status.QUEUED) +function OPSTRANSPORT:IsQueued(Legion) + local is=self:is(OPSTRANSPORT.Status.QUEUED) + if Legion then + is=self:GetLegionStatus(Legion)==OPSTRANSPORT.Status.QUEUED + end + return is end --- Check if state is REQUESTED. -- @param #OPSTRANSPORT self +-- @param Ops.Legion#LEGION Legion (Optional) Check if transport is queued at this legion. -- @return #boolean If true, status is REQUESTED. -function OPSTRANSPORT:IsRequested() - return self:is(OPSTRANSPORT.Status.REQUESTED) +function OPSTRANSPORT:IsRequested(Legion) + local is=self:is(OPSTRANSPORT.Status.REQUESTED) + if Legion then + is=self:GetLegionStatus(Legion)==OPSTRANSPORT.Status.REQUESTED + end + return is end --- Check if state is SCHEDULED. -- @param #OPSTRANSPORT self -- @return #boolean If true, status is SCHEDULED. function OPSTRANSPORT:IsScheduled() - return self:is(OPSTRANSPORT.Status.SCHEDULED) + local is=self:is(OPSTRANSPORT.Status.SCHEDULED) + return is end --- Check if state is EXECUTING. -- @param #OPSTRANSPORT self -- @return #boolean If true, status is EXECUTING. function OPSTRANSPORT:IsExecuting() - return self:is(OPSTRANSPORT.Status.EXECUTING) + local is=self:is(OPSTRANSPORT.Status.EXECUTING) + return is end --- Check if all cargo was delivered (or is dead). @@ -1314,7 +1463,7 @@ function OPSTRANSPORT:onafterStatus(From, Event, To) if self.verbose>=1 then -- Info text. - local text=string.format("%s: Ncargo=%d/%d, Ncarrier=%d/%d", fsmstate:upper(), self.Ncargo, self.Ndelivered, #self.carriers, self.Ncarrier) + local text=string.format("%s: Ncargo=%d/%d, Ncarrier=%d/%d, Nlegions=%d", fsmstate:upper(), self.Ncargo, self.Ndelivered, #self.carriers, self.Ncarrier, #self.legions) -- Info about cargo and carrier. if self.verbose>=2 then @@ -1701,13 +1850,17 @@ function OPSTRANSPORT:_GetTransportZoneCombo(Carrier) -- Check that pickup and deploy zones were defined. if tz.PickupZone and tz.DeployZone and tz.EmbarkZone then + --TODO: Check if Carrier is an aircraft and if so, check that pickup AND deploy zones are airbases (not ships, not farps). + -- Count undelivered cargos in embark(!) zone that fit into the carrier. local ncargo=self:_CountCargosInZone(tz.EmbarkZone, false, Carrier, tz) --env.info(string.format("FF GetPickupZone i=%d, ncargo=%d", i, ncargo)) - if ncargo>0 then + -- At least one group in the zone. + if ncargo>=1 then + -- Distance to the carrier in meters. local dist=tz.PickupZone:Get2DDistance(vec2) if distmin==nil or dist