COMMANDER

- Added OPS transport (untested)
This commit is contained in:
Frank 2021-09-13 08:31:00 +02:00
parent 6a6cb1961d
commit 1d0eb9806d
9 changed files with 672 additions and 257 deletions

View File

@ -2224,6 +2224,20 @@ do -- ZONE_AIRBASE
self._.ZoneAirbase = Airbase
self._.ZoneVec2Cache = self._.ZoneAirbase:GetVec2()
if Airbase:IsShip() then
self.isShip=true
self.isHelipad=false
self.isAirdrome=false
elseif Airbase:IsHelipad() then
self.isShip=false
self.isHelipad=true
self.isAirdrome=false
elseif Airbase:IsAirdrome() then
self.isShip=false
self.isHelipad=false
self.isAirdrome=true
end
-- Zone objects are added to the _DATABASE and SET_ZONE objects.
_EVENTDISPATCHER:CreateEventNewZone( self )

View File

@ -1866,6 +1866,9 @@ function WAREHOUSE:New(warehouse, alias)
self.isunit=false
else
self.isunit=true
if warehouse:IsShip() then
self.isShip=true
end
end
end
@ -1909,8 +1912,14 @@ function WAREHOUSE:New(warehouse, alias)
end
-- Define warehouse and default spawn zone.
if self.isShip then
self.zone=ZONE_AIRBASE:New(self.warehouse:GetName(), 1000)
self.spawnzone=ZONE_AIRBASE:New(self.warehouse:GetName(), 1000)
else
self.zone=ZONE_RADIUS:New(string.format("Warehouse zone %s", self.warehouse:GetName()), warehouse:GetVec2(), 500)
self.spawnzone=ZONE_RADIUS:New(string.format("Warehouse %s spawn zone", self.warehouse:GetName()), warehouse:GetVec2(), 250)
end
-- Defaults
self:SetMarker(true)
@ -4507,6 +4516,11 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request)
return
end
-- Trigger event.
if spawngroup then
self:__AssetSpawned(0.01, spawngroup, _assetitem, Request)
end
end
-- Init problem table.
@ -5336,24 +5350,6 @@ function WAREHOUSE:onafterRunwayRepaired(From, Event, To)
end
--- On before "AssetSpawned" event. Checks whether the asset was already set to "spawned" for groups with multiple units.
-- @param #WAREHOUSE self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Wrapper.Group#GROUP group The group spawned.
-- @param #WAREHOUSE.Assetitem asset The asset that is dead.
-- @param #WAREHOUSE.Pendingitem request The request of the dead asset.
function WAREHOUSE:onbeforeAssetSpawned(From, Event, To, group, asset, request)
if asset.spawned then
--return false
else
--return true
end
return true
end
--- On after "AssetSpawned" event triggered when an asset group is spawned into the cruel world.
-- @param #WAREHOUSE self
-- @param #string From From state.
@ -5369,6 +5365,24 @@ function WAREHOUSE:onafterAssetSpawned(From, Event, To, group, asset, request)
-- Sete asset state to spawned.
asset.spawned=true
-- Set spawn group name.
asset.spawngroupname=group:GetName()
-- Remove asset from stock.
self:_DeleteStockItem(asset)
-- Add group.
if asset.iscargo==true then
request.cargogroupset=request.cargogroupset or SET_GROUP:New()
request.cargogroupset:AddGroup(group)
else
request.transportgroupset=request.transportgroupset or SET_GROUP:New()
request.transportgroupset:AddGroup(group)
end
-- Set warehouse state.
group:SetState(group, "WAREHOUSE", self)
-- Check if all assets groups are spawned and trigger events.
local n=0
for _,_asset in pairs(request.assets) do
@ -5718,15 +5732,15 @@ function WAREHOUSE:_SpawnAssetRequest(Request)
if asset.category==Group.Category.GROUND then
-- Spawn ground troops.
_group=self:_SpawnAssetGroundNaval(_alias, asset, Request, self.spawnzone)
_group=self:_SpawnAssetGroundNaval(_alias, asset, Request, self.spawnzone, Request.lateActivation)
elseif asset.category==Group.Category.AIRPLANE or asset.category==Group.Category.HELICOPTER then
-- Spawn air units.
if Parking[asset.uid] then
_group=self:_SpawnAssetAircraft(_alias, asset, Request, Parking[asset.uid], UnControlled)
_group=self:_SpawnAssetAircraft(_alias, asset, Request, Parking[asset.uid], UnControlled, Request.lateActivation)
else
_group=self:_SpawnAssetAircraft(_alias, asset, Request, nil, UnControlled)
_group=self:_SpawnAssetAircraft(_alias, asset, Request, nil, UnControlled, Request.lateActivation)
end
elseif asset.category==Group.Category.TRAIN then
@ -5736,7 +5750,7 @@ function WAREHOUSE:_SpawnAssetRequest(Request)
--TODO: Rail should only get one asset because they would spawn on top!
-- Spawn naval assets.
_group=self:_SpawnAssetGroundNaval(_alias, asset, Request, self.spawnzone)
_group=self:_SpawnAssetGroundNaval(_alias, asset, Request, self.spawnzone, Request.lateActivation)
end
--self:E(self.lid.."ERROR: Spawning of TRAIN assets not possible yet!")
@ -5744,12 +5758,17 @@ function WAREHOUSE:_SpawnAssetRequest(Request)
elseif asset.category==Group.Category.SHIP then
-- Spawn naval assets.
_group=self:_SpawnAssetGroundNaval(_alias, asset, Request, self.portzone)
_group=self:_SpawnAssetGroundNaval(_alias, asset, Request, self.portzone, Request.lateActivation)
else
self:E(self.lid.."ERROR: Unknown asset category!")
end
-- Trigger event.
if _group then
self:__AssetSpawned(0.01, _group, asset, Request)
end
end
end
@ -5761,9 +5780,9 @@ end
-- @param #WAREHOUSE.Assetitem asset Ground asset that will be spawned.
-- @param #WAREHOUSE.Queueitem request Request belonging to this asset. Needed for the name/alias.
-- @param Core.Zone#ZONE spawnzone Zone where the assets should be spawned.
-- @param #boolean aioff If true, AI of ground units are set to off.
-- @param #boolean lateactivated If true, groups are spawned late activated.
-- @return Wrapper.Group#GROUP The spawned group or nil if the group could not be spawned.
function WAREHOUSE:_SpawnAssetGroundNaval(alias, asset, request, spawnzone, aioff)
function WAREHOUSE:_SpawnAssetGroundNaval(alias, asset, request, spawnzone, lateactivated)
if asset and (asset.category==Group.Category.GROUND or asset.category==Group.Category.SHIP or asset.category==Group.Category.TRAIN) then
@ -5807,6 +5826,11 @@ function WAREHOUSE:_SpawnAssetGroundNaval(alias, asset, request, spawnzone, aiof
end
-- Late activation.
template.lateActivation=lateactivated
env.info("FF lateActivation="..tostring(template.lateActivation))
template.route.points[1].x = coord.x
template.route.points[1].y = coord.z
@ -5817,14 +5841,6 @@ function WAREHOUSE:_SpawnAssetGroundNaval(alias, asset, request, spawnzone, aiof
-- Spawn group.
local group=_DATABASE:Spawn(template) --Wrapper.Group#GROUP
-- Activate group. Should only be necessary for late activated groups.
--group:Activate()
-- Switch AI off if desired. This works only for ground and naval groups.
if aioff then
group:SetAIOff()
end
return group
end
@ -5838,8 +5854,9 @@ end
-- @param #WAREHOUSE.Queueitem request Request belonging to this asset. Needed for the name/alias.
-- @param #table parking Parking data for this asset.
-- @param #boolean uncontrolled Spawn aircraft in uncontrolled state.
-- @param #boolean lateactivated If true, groups are spawned late activated.
-- @return Wrapper.Group#GROUP The spawned group or nil if the group could not be spawned.
function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrolled)
function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrolled, lateactivated)
if asset and asset.category==Group.Category.AIRPLANE or asset.category==Group.Category.HELICOPTER then
@ -6330,53 +6347,12 @@ function WAREHOUSE:_OnEventBirth(EventData)
if asset and request then
if asset.spawned and type(asset.spawned)=="boolean" and asset.spawned==true then
return
end
-- Debug message.
self:T(self.lid..string.format("Warehouse %s captured event birth of request ID=%d, asset ID=%d, unit %s spawned=%s", self.alias, request.uid, asset.uid, EventData.IniUnitName, tostring(asset.spawned)))
-- Set born to true.
request.born=true
if not asset.spawned then
asset.spawned=1
else
asset.spawned=asset.spawned+1
end
-- Birth is triggered for each unit. We need to make sure not to call this too often!
if asset.spawned==asset.nunits then
-- Remove asset from stock.
self:_DeleteStockItem(asset)
-- Set spawned switch.
asset.spawned=true
asset.spawngroupname=group:GetName()
-- Add group.
if asset.iscargo==true then
request.cargogroupset=request.cargogroupset or SET_GROUP:New()
request.cargogroupset:AddGroup(group)
else
request.transportgroupset=request.transportgroupset or SET_GROUP:New()
request.transportgroupset:AddGroup(group)
end
-- Set warehouse state.
group:SetState(group, "WAREHOUSE", self)
-- Asset spawned FSM function.
-- This needs to be delayed a bit for all units to be present. Especially, since MOOSE needs a birth event for UNITs to be added to the _DATABASE.
self:__AssetSpawned(0.1, group, asset, request)
--self:AssetSpawned(group, asset, request)
end
else
self:E(self.lid..string.format("ERROR: Either asset AID=%s or request RID=%s are nil in event birth of unit %s", tostring(aid), tostring(rid), tostring(EventData.IniUnitName)))
end
@ -7076,10 +7052,9 @@ function WAREHOUSE:_CheckRequestValid(request)
-- Check that both spawn zones are not in water.
local inwater=self.spawnzone:GetCoordinate():IsSurfaceTypeWater() or request.warehouse.spawnzone:GetCoordinate():IsSurfaceTypeWater()
if inwater then
if inwater and not request.lateActivation then
self:E("ERROR: Incorrect request. Ground asset requested but at least one spawn zone is in water!")
--valid=false
valid=false
return false
end
-- No ground assets directly to or from ships.

View File

@ -299,6 +299,32 @@ function CHIEF:RemoveMission(Mission)
return self
end
--- Add transport to transport queue of the COMMANDER.
-- @param #CHIEF self
-- @param Ops.OpsTransport#OPSTRANSPORT Transport Transport to be added.
-- @return #CHIEF self
function CHIEF:AddOpsTransport(Transport)
Transport.chief=self
self.commander:AddOpsTransport(Transport)
return self
end
--- Remove transport from queue.
-- @param #CHIEF self
-- @param Ops.OpsTransport#OPSTRANSPORT Transport Transport to be removed.
-- @return #CHIEF self
function CHIEF:RemoveTransport(Transport)
Transport.chief=nil
self.commander:RemoveTransport(Transport)
return self
end
--- Add target.
-- @param #CHIEF self
-- @param Ops.Target#TARGET Target Target object to be added.

View File

@ -19,6 +19,7 @@
-- @field #string lid Class id string for output to DCS log file.
-- @field #table legions Table of legions which are commanded.
-- @field #table missionqueue Mission queue.
-- @field #table transportqueue Transport queue.
-- @field Ops.ChiefOfStaff#CHIEF chief Chief of staff.
-- @extends Core.Fsm#FSM
@ -37,6 +38,7 @@ COMMANDER = {
verbose = 0,
legions = {},
missionqueue = {},
transportqueue = {},
}
--- COMMANDER class version.
@ -80,6 +82,9 @@ function COMMANDER:New()
self:AddTransition("*", "MissionAssign", "*") -- Mission is assigned to a or multiple LEGIONs.
self:AddTransition("*", "MissionCancel", "*") -- COMMANDER cancels a mission.
self:AddTransition("*", "TransportAssign", "*") -- Transport is assigned to a or multiple LEGIONs.
self:AddTransition("*", "TransportCancel", "*") -- COMMANDER cancels a Transport.
------------------------
--- Pseudo Functions ---
------------------------
@ -155,6 +160,43 @@ function COMMANDER:New()
-- @param #string To To state.
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
--- Triggers the FSM event "TransportAssign" after a delay.
-- @function [parent=#COMMANDER] __TransportAssign
-- @param #COMMANDER self
-- @param #number delay Delay in seconds.
-- @param Ops.Legion#LEGION Legion The Legion.
-- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport.
--- On after "TransportAssign" event.
-- @function [parent=#COMMANDER] OnAfterTransportAssign
-- @param #COMMANDER self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Ops.Legion#LEGION Legion The Legion.
-- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport.
--- Triggers the FSM event "TransportCancel".
-- @function [parent=#COMMANDER] TransportCancel
-- @param #COMMANDER self
-- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport.
--- Triggers the FSM event "TransportCancel" after a delay.
-- @function [parent=#COMMANDER] __TransportCancel
-- @param #COMMANDER self
-- @param #number delay Delay in seconds.
-- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport.
--- On after "TransportCancel" event.
-- @function [parent=#COMMANDER] OnAfterTransportCancel
-- @param #COMMANDER self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport.
return self
end
@ -225,6 +267,21 @@ function COMMANDER:AddMission(Mission)
return self
end
--- Add transport to queue.
-- @param #COMMANDER self
-- @param Ops.OpsTransport#OPSTRANSPORT Transport The OPS transport to be added.
-- @return #COMMANDER self
function COMMANDER:AddOpsTransport(Transport)
Transport.commander=self
Transport.statusCommander=TRANSPORT.Status.PLANNED
table.insert(self.transportqueue, Transport)
return self
end
--- Remove mission from queue.
-- @param #COMMANDER self
-- @param Ops.Auftrag#AUFTRAG Mission Mission to be removed.
@ -246,6 +303,27 @@ function COMMANDER:RemoveMission(Mission)
return self
end
--- Remove transport from queue.
-- @param #COMMANDER self
-- @param Ops.OpsTransport#OPSTRANSPORT Transport The OPS transport to be removed.
-- @return #COMMANDER self
function COMMANDER:RemoveTransport(Transport)
for i,_transport in pairs(self.transportqueue) do
local transport=_transport --Ops.OpsTransport#OPSTRANSPORT
if transport.uid==Transport.uid then
self:I(self.lid..string.format("Removing mission %s (%s) status=%s from queue", transport.uid, transport:GetState()))
transport.commander=nil
table.remove(self.transportqueue, i)
break
end
end
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Start & Status
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -293,6 +371,8 @@ function COMMANDER:onafterStatus(From, Event, To)
-- Check mission queue and assign one PLANNED mission.
self:CheckMissionQueue()
-- Check mission queue and assign one PLANNED mission
---
-- LEGIONS
---
@ -388,6 +468,20 @@ function COMMANDER:onafterStatus(From, Event, To)
self:I(self.lid..text)
end
---
-- TRANSPORTS
---
-- Transport queue.
if self.verbose>=2 and #self.transportqueue>0 then
local text="Transport queue:"
for i,_transport in pairs(self.transportqueue) do
local transport=_transport --Ops.OpsTransport#OPSTRANSPORT
text=text..string.format("\n[%d] UID=%d: status=%s", i, transport.uid, transport:GetState())
end
self:I(self.lid..text)
end
self:__Status(-30)
end
@ -455,8 +549,31 @@ function COMMANDER:onafterMissionCancel(From, Event, To, Mission)
end
--- On after "MissionAssign" event. Mission is added to a LEGION mission queue.
-- @param #COMMANDER self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Ops.Legion#LEGION Legion The LEGION.
-- @param Ops.OpsTransport#OPSTRANSPORT
function COMMANDER:onafterTransportAssign(From, Event, To, Legion, Transport)
-- Debug info.
self:I(self.lid..string.format("Assigning transport %d to legion %s", Transport.uid, Legion.alias))
-- Set mission commander status to QUEUED as it is now queued at a legion.
Transport.statusCommander=OPSTRANSPORT.Status.QUEUED
-- Add mission to legion.
Legion:AddOpsTransport(Transport)
-- Directly request the mission as the assets have already been selected.
Legion:TransportRequest(Transport)
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Resources
-- Mission Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Check mission queue and assign ONE planned mission.
@ -538,113 +655,6 @@ function COMMANDER:CheckMissionQueue()
end
--- Check all legions if they are able to do a specific mission type at a certain location with a given number of assets.
-- @param #COMMANDER self
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
-- @return #table Table of LEGIONs that can do the mission and have at least one asset available right now.
function COMMANDER:GetLegionsForMission(Mission)
-- Table of legions that can do the mission.
local legions={}
-- Loop over all legions.
for _,_legion in pairs(self.legions) do
local legion=_legion --Ops.Legion#LEGION
-- Count number of assets in stock.
local Nassets=0
if legion:IsAirwing() then
Nassets=legion:CountAssetsWithPayloadsInStock(Mission.payloads, {Mission.type}, Attributes)
else
Nassets=legion:CountAssets(true, {Mission.type}, Attributes) --Could also specify the attribute if Air or Ground mission.
end
-- Has it assets that can?
if Nassets>0 and false then
-- Get coordinate of the target.
local coord=Mission:GetTargetCoordinate()
if coord then
-- Distance from legion to target.
local distance=UTILS.MetersToNM(coord:Get2DDistance(legion:GetCoordinate()))
-- Round: 55 NM ==> 5.5 ==> 6, 63 NM ==> 6.3 ==> 6
local dist=UTILS.Round(distance/10, 0)
-- Debug info.
self:I(self.lid..string.format("Got legion %s with Nassets=%d and dist=%.1f NM, rounded=%.1f", legion.alias, Nassets, distance, dist))
-- Add legion to table of legions that can.
table.insert(legions, {airwing=legion, distance=distance, dist=dist, targetcoord=coord, nassets=Nassets})
end
end
-- Add legion if it can provide at least 1 asset.
if Nassets>0 then
table.insert(legions, legion)
end
end
return legions
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 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:CountAssets(InStock, MissionTypes, Attributes)
local N=0
for _,_legion in pairs(self.legions) do
local legion=_legion --Ops.Legion#LEGION
N=N+legion:CountAssets(InStock, MissionTypes, Attributes)
end
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
--- Recruit assets for a given mission.
-- @param #COMMANDER self
@ -757,7 +767,7 @@ function COMMANDER:RecruitAssets(Mission)
-- Add assets to mission.
for i=1,Nassets do
local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem
self:T(self.lid..string.format("Adding asset %s to mission %s [%s]", asset.spawngroupname, Mission.name, Mission.type))
asset.isReserved=true
Mission:AddAsset(asset)
Legions[asset.legion.alias]=asset.legion
end
@ -832,6 +842,393 @@ function COMMANDER:_OptimizeAssetSelection(assets, Mission, includePayload)
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Transport Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Check transport queue and assign ONE planned transport.
-- @param #COMMANDER self
function COMMANDER:CheckTransportQueue()
-- Number of missions.
local Ntransports=#self.transportqueue
-- Treat special cases.
if Ntransports==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<taskB.prio) or (taskA.prio==taskB.prio and taskA.Tstart<taskB.Tstart)
end
table.sort(self.transportqueue, _sort)
-- Get the lowest importance value (lower means more important).
-- If a mission with importance 1 exists, mission with importance 2 will not be assigned. Missions with no importance (nil) can still be selected.
local vip=math.huge
for _,_transport in pairs(self.transportqueue) do
local transport=_transport --Ops.OpsTransport#OPSTRANSPORT
if transport.importance and transport.importance<vip then
vip=transport.importance
end
end
for _,_transport in pairs(self.transportqueue) do
local transport=_transport --Ops.OpsTransport#OPSTRANSPORT
-- We look for PLANNED missions.
if transport:IsPlanned() and transport:IsReadyToGo() and (transport.importance==nil or transport.importance<=vip) then
---
-- PLANNNED Mission
--
-- 1. Select best assets from legions
-- 2. Assign mission to legions that have the best assets.
---
-- Recruite assets from legions.
local recruited, legions=self:RecruitAssetsForTransport(transport)
if recruited then
for _,_legion in pairs(legions) do
local legion=_legion --Ops.Legion#LEGION
-- Debug message.
self:I(self.lid..string.format("Assigning transport UID=%d to legion %s", transport.uid, legion.alias))
-- Add mission to legion.
self:TransportAssign(legion, transport)
end
-- Only ONE transport is assigned.
return
end
else
---
-- Missions NOT in PLANNED state
---
end
end
end
--- Recruit assets for a given transport.
-- @param #COMMANDER self
-- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport.
-- @return #boolean If `true`, enough assets could be recruited.
-- @return #table Legions that have recruited assets.
function COMMANDER:RecruitAssetsForTransport(Transport)
-- Get all undelivered cargo ops groups.
local cargoOpsGroups=Transport:GetCargoOpsGroups(false)
local weightGroup=0
-- At least one group should be spawned.
if #cargoOpsGroups>0 then
-- Calculate the max weight so we know which cohorts can provide carriers.
for _,_opsgroup in pairs(cargoOpsGroups) do
local opsgroup=_opsgroup --Ops.OpsGroup#OPSGROUP
local weight=opsgroup:GetWeightTotal()
if weight>weightGroup then
weightGroup=weight
end
end
else
-- No cargo groups!
return false, {}
end
-- The recruited assets.
local Assets={}
-- Legions we consider for selecting assets.
local legions=self.legions
--TODO: Setting of Mission.squadrons (cohorts) will not work here!
-- Legions which have the best assets for the Mission.
local Legions={}
for _,_legion in pairs(legions) do
local legion=_legion --Ops.Legion#LEGION
-- Number of payloads in stock per aircraft type.
local Npayloads={}
-- First get payloads for aircraft types of squadrons.
for _,_cohort in pairs(legion.cohorts) do
local cohort=_cohort --Ops.Cohort#COHORT
if Npayloads[cohort.aircrafttype]==nil then
Npayloads[cohort.aircrafttype]=legion:IsAirwing() and legion:CountPayloadsInStock(AUFTRAG.Type.OPSTRANSPORT, cohort.aircrafttype) or 999
self:I(self.lid..string.format("Got N=%d payloads for mission type %s [%s]", Npayloads[cohort.aircrafttype], AUFTRAG.Type.OPSTRANSPORT, cohort.aircrafttype))
end
end
-- Loops over cohorts.
for _,_cohort in pairs(legion.cohorts) do
local cohort=_cohort --Ops.Cohort#COHORT
local npayloads=Npayloads[cohort.aircrafttype]
if cohort:IsOnDuty() and npayloads>0 and cohort:CheckMissionCapability({AUFTRAG.Type.OPSTRANSPORT}) and cohort.cargobayLimit>=weightGroup then
-- Recruit assets from squadron.
local assets, npayloads=cohort:RecruitAssets(AUFTRAG.Type.OPSTRANSPORT, npayloads)
Npayloads[cohort.aircrafttype]=npayloads
for _,asset in pairs(assets) do
table.insert(Assets, asset)
end
end
end
end
-- Now we have a long list with assets.
self:_OptimizeAssetSelectionForTransport(Assets, Transport)
for _,_asset in pairs(Assets) do
local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem
if asset.legion:IsAirwing() then
-- Only assets that have no payload. Should be only spawned assets!
if not asset.payload then
-- Fetch payload for asset. This can be nil!
asset.payload=asset.legion:FetchPayloadFromStock(asset.unittype, AUFTRAG.Type.OPSTRANSPORT)
end
end
end
-- Remove assets that dont have a payload.
for i=#Assets,1,-1 do
local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem
if asset.legion:IsAirwing() and not asset.payload then
table.remove(Assets, i)
end
end
-- Number of required carriers.
local NreqMin,NreqMax=Transport:GetRequiredCarriers()
-- Number of assets. At most NreqMax.
local Nassets=math.min(#Assets, NreqMax)
if Nassets>=NreqMin then
---
-- Found enough assets
---
-- Add assets to transport.
for i=1,Nassets do
local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem
asset.isReserved=true
Transport:AddAsset(asset)
Legions[asset.legion.alias]=asset.legion
end
-- Return payloads of not needed assets.
for i=Nassets+1,#Assets do
local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem
if asset.legion:IsAirwing() and not asset.spawned then
self:T(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname))
asset.legion:ReturnPayloadFromAsset(asset)
end
end
-- Found enough assets.
return true, Legions
else
---
-- NOT enough assets
---
-- Return payloads of assets.
if self:IsAirwing() then
for i=1,#Assets do
local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem
if asset.legion:IsAirwing() and not asset.spawned then
self:T2(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname))
asset.legion:ReturnPayloadFromAsset(asset)
end
end
end
-- Not enough assets found.
return false, {}
end
return nil, {}
end
--- Optimize chosen assets for the given transport.
-- @param #COMMANDER self
-- @param #table assets Table of (unoptimized) assets.
-- @param Ops.OpsTransport#OPSTRANSPORT Transport Transport assignment.
function COMMANDER:_OptimizeAssetSelectionForTransport(assets, Transport)
-- Calculate the mission score of all assets.
for _,_asset in pairs(assets) do
local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem
asset.score=asset.legion:CalculateAssetTransportScore(asset, Transport)
end
--- Sort assets wrt to their mission score. Higher is better.
local function optimize(a, b)
local assetA=a --Functional.Warehouse#WAREHOUSE.Assetitem
local assetB=b --Functional.Warehouse#WAREHOUSE.Assetitem
-- Higher score wins. If equal score ==> closer wins.
return (assetA.score>assetB.score)
end
table.sort(assets, optimize)
-- Remove distance parameter.
local text=string.format("Optimized %d assets for %s mission (payload=%s):", #assets, Mission.type, tostring(includePayload))
for i,Asset in pairs(assets) do
local asset=Asset --Functional.Warehouse#WAREHOUSE.Assetitem
text=text..string.format("\n%s %s: score=%d", asset.squadname, asset.spawngroupname, asset.score)
asset.dist=nil
asset.score=nil
end
self:T2(self.lid..text)
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Resources
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- 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 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:CountAssets(InStock, MissionTypes, Attributes)
local N=0
for _,_legion in pairs(self.legions) do
local legion=_legion --Ops.Legion#LEGION
N=N+legion:CountAssets(InStock, MissionTypes, Attributes)
end
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
--- Check all legions if they are able to do a specific mission type at a certain location with a given number of assets.
-- @param #COMMANDER self
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
-- @return #table Table of LEGIONs that can do the mission and have at least one asset available right now.
function COMMANDER:GetLegionsForMission(Mission)
-- Table of legions that can do the mission.
local legions={}
-- Loop over all legions.
for _,_legion in pairs(self.legions) do
local legion=_legion --Ops.Legion#LEGION
-- Count number of assets in stock.
local Nassets=0
if legion:IsAirwing() then
Nassets=legion:CountAssetsWithPayloadsInStock(Mission.payloads, {Mission.type}, Attributes)
else
Nassets=legion:CountAssets(true, {Mission.type}, Attributes) --Could also specify the attribute if Air or Ground mission.
end
-- Has it assets that can?
if Nassets>0 and false then
-- Get coordinate of the target.
local coord=Mission:GetTargetCoordinate()
if coord then
-- Distance from legion to target.
local distance=UTILS.MetersToNM(coord:Get2DDistance(legion:GetCoordinate()))
-- Round: 55 NM ==> 5.5 ==> 6, 63 NM ==> 6.3 ==> 6
local dist=UTILS.Round(distance/10, 0)
-- Debug info.
self:I(self.lid..string.format("Got legion %s with Nassets=%d and dist=%.1f NM, rounded=%.1f", legion.alias, Nassets, distance, dist))
-- Add legion to table of legions that can.
table.insert(legions, {airwing=legion, distance=distance, dist=dist, targetcoord=coord, nassets=Nassets})
end
end
-- Add legion if it can provide at least 1 asset.
if Nassets>0 then
table.insert(legions, legion)
end
end
return legions
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@ -1374,16 +1374,17 @@ end
-- @param Wrapper.Airbase#AIRBASE.ParkingSpot Spot Parking Spot.
function FLIGHTGROUP:onafterElementParking(From, Event, To, Element, Spot)
-- Set parking spot.
if Spot then
self:_SetElementParkingAt(Element, Spot)
end
-- Debug info.
self:T(self.lid..string.format("Element parking %s at spot %s", Element.name, Element.parking and tostring(Element.parking.TerminalID) or "N/A"))
-- Set element status.
self:_UpdateStatus(Element, OPSGROUP.ElementStatus.PARKING)
if Spot then
self:_SetElementParkingAt(Element, Spot)
end
if self:IsTakeoffCold() then
-- Wait for engine startup event.
elseif self:IsTakeoffHot() then
@ -1660,12 +1661,17 @@ end
-- @param #string Event Event.
-- @param #string To To state.
function FLIGHTGROUP:onafterParking(From, Event, To)
self:T(self.lid..string.format("Flight is parking"))
-- Get closest airbase
local airbase=self:GetClosestAirbase() --self.group:GetCoordinate():GetClosestAirbase()
local airbasename=airbase:GetName() or "unknown"
-- Debug info
self:T(self.lid..string.format("Flight is parking at airbase %s", airbasename))
-- Set current airbase.
self.currbase=airbase
-- Parking time stamp.
self.Tparking=timer.getAbsTime()

View File

@ -256,8 +256,11 @@ function LEGION:AddMission(Mission)
-- Add ops transport to transport Legions.
if Mission.opstransport then
local PickupZone=self.spawnzone
local DeployZone=Mission.opstransport.tzcDefault.DeployZone
-- Add a new TZC: from pickup here to the deploy zone.
local tzc=Mission.opstransport:AddTransportZoneCombo(self.spawnzone, Mission.opstransport.tzcDefault.DeployZone)
local tzc=Mission.opstransport:AddTransportZoneCombo(PickupZone, DeployZone)
--TODO: Depending on "from where to where" the assets need to transported, we need to set ZONE_AIRBASE etc.
@ -601,6 +604,17 @@ function LEGION:onafterMissionRequest(From, Event, To, Mission)
-- The queueid has been increased in the onafterAddRequest function. So we can simply use it here.
Mission.requestID[self.alias]=self.queueid
-- Get request.
local request=self:GetRequestByID(self.queueid)
if request then
if self.isShip then
self:T(self.lid.."FF request late activated")
request.lateActivation=true
end
end
end
end
@ -653,7 +667,6 @@ 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[self.alias]=self.queueid
end
end
@ -1571,7 +1584,7 @@ function LEGION:RecruitAssets(Mission)
MissionType=Mission.alert5MissionType
end
Npayloads[cohort.aircrafttype]=self:IsAirwing() and self:CountPayloadsInStock(MissionType, cohort.aircrafttype, Mission.payloads) or 999
self:I(self.lid..string.format("Got N=%d payloads for mission type=%s and unit type=%s", Npayloads[cohort.aircrafttype], MissionType, cohort.aircrafttype))
self:T2(self.lid..string.format("Got N=%d payloads for mission type=%s and unit type=%s", Npayloads[cohort.aircrafttype], MissionType, cohort.aircrafttype))
end
end
@ -1818,6 +1831,9 @@ function LEGION:RecruitAssetsForTransport(Transport)
weightGroup=weight
end
end
else
-- No cargo groups!
return false
end
@ -1829,7 +1845,7 @@ function LEGION:RecruitAssetsForTransport(Transport)
local cohort=_cohort --Ops.Cohort#COHORT
if Npayloads[cohort.aircrafttype]==nil then
Npayloads[cohort.aircrafttype]=self:IsAirwing() and self:CountPayloadsInStock(AUFTRAG.Type.OPSTRANSPORT, cohort.aircrafttype) or 999
self:I(self.lid..string.format("Got N=%d payloads for mission type=%s and unit type=%s", Npayloads[cohort.aircrafttype], AUFTRAG.Type.OPSTRANSPORT, cohort.aircrafttype))
self:T2(self.lid..string.format("Got N=%d payloads for mission type=%s and unit type=%s", Npayloads[cohort.aircrafttype], AUFTRAG.Type.OPSTRANSPORT, cohort.aircrafttype))
end
end
@ -1858,7 +1874,7 @@ function LEGION:RecruitAssetsForTransport(Transport)
end
-- Sort asset list. Best ones come first.
self:_OptimizeAssetSelectionForTransport(Assets, Transport, false)
self:_OptimizeAssetSelectionForTransport(Assets, Transport)
-- If airwing, get the best payload available.
if self:IsAirwing() then
@ -2019,7 +2035,8 @@ function LEGION:CalculateAssetTransportScore(asset, Transport)
-- Reduce score for legions that are futher away.
score=score-distance
--TODO: Check cargo bay capacity.
-- Add 1 score point for each 10 kg of cargo bay.
score=score+UTILS.Round(asset.cargobaymax/10, 0)
--TODO: Check ALERT 5 for Transports.
if asset.spawned then

View File

@ -467,12 +467,12 @@ OPSGROUP.version="0.7.5"
-- TODO: Invisible/immortal.
-- TODO: F10 menu.
-- TODO: Add pseudo function.
-- TODO: Options EPLRS
-- TODO: Afterburner restrict
-- TODO: What more options?
-- TODO: Damage?
-- TODO: Shot events?
-- TODO: Marks to add waypoints/tasks on-the-fly.
-- DONE: Options EPLRS
-- DONE: A lot.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -3344,8 +3344,9 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task)
-- Target
local target=Task.dcstask.params.target --Ops.Target#TARGET
Task.dcstask.params.lastindex=1
self.lastindex=1
-- Target object and zone.
local object=target.targets[1] --Ops.Target#TARGET.Object
local zone=object.Object --Core.Zone#ZONE
@ -4495,9 +4496,10 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint)
-- SPECIAL TASK: Recon Mission
---
-- TARGET.
local target=task.dcstask.params.target --Ops.Target#TARGET
local n=task.dcstask.params.lastindex+1
local n=self.lastindex+1
if n<=#target.targets then
@ -4528,7 +4530,7 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint)
wp.missionUID=mission and mission.auftragsnummer or nil
-- Increase counter.
task.dcstask.params.lastindex=task.dcstask.params.lastindex+1
self.lastindex=self.lastindex+1
else
@ -4599,7 +4601,7 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint)
-- Passing mission waypoint?
if Waypoint.missionUID then
self:T(self.lid.."FF passing mission waypoint")
self:T2(self.lid..string.format("Passing mission waypoint"))
end
-- Check if all tasks/mission are done?
@ -6636,23 +6638,16 @@ function OPSGROUP:onafterPickup(From, Event, To)
-- Flight Group
---
if airbasePickup then
---
-- Pickup at airbase
---
-- Current airbase.
local airbaseCurrent=self.currbase
if airbaseCurrent then
-- Activate uncontrolled group.
if self:IsParking() and self:IsUncontrolled() then
self:StartUncontrolled()
end
end
if airbasePickup then
---
-- Pickup at airbase
---
-- Order group to land at an airbase.
self:__LandAtAirbase(-0.1, airbasePickup)
@ -6663,11 +6658,6 @@ function OPSGROUP:onafterPickup(From, Event, To)
-- Helo can also land in a zone (NOTE: currently VTOL cannot!)
---
-- Activate uncontrolled group.
if self:IsParking() and self:IsUncontrolled() then
self:StartUncontrolled(0.5)
end
-- If this is a helo and no ZONE_AIRBASE was given, we make the helo land in the pickup zone.
local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid, UTILS.MetersToFeet(self.altitudeCruise), false) ; waypoint.detour=1
@ -7004,22 +6994,16 @@ function OPSGROUP:onafterTransport(From, Event, To)
-- Add waypoint.
if self:IsFlightgroup() then
if airbaseDeploy then
---
-- Deploy at airbase
---
local airbaseCurrent=self.currbase
if airbaseCurrent then
-- Activate uncontrolled group.
if self:IsParking() and self:IsUncontrolled() then
self:StartUncontrolled()
end
end
if airbaseDeploy then
---
-- Deploy at airbase
---
-- Order group to land at an airbase.
self:__LandAtAirbase(-0.1, airbaseDeploy)

View File

@ -53,6 +53,8 @@
-- @field #table assets Warehouse assets assigned for this transport.
-- @field #table legions Assigned legions.
-- @field #table statusLegion Transport status of all assigned LEGIONs.
-- @field #string statusCommander Staus of the COMMANDER.
-- @field Ops.Commander#COMMANDER commander Commander of the transport.
-- @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
@ -133,6 +135,9 @@ OPSTRANSPORT = {
-- @field #string SCHEDULED Transport is scheduled in the cargo queue.
-- @field #string EXECUTING Transport is being executed.
-- @field #string DELIVERED Transport was delivered.
-- @field #string CANCELLED Transport was cancelled.
-- @field #string SUCCESS Transport was a success.
-- @field #string FAILED Transport failed.
OPSTRANSPORT.Status={
PLANNED="planned",
QUEUED="queued",
@ -140,6 +145,9 @@ OPSTRANSPORT.Status={
SCHEDULED="scheduled",
EXECUTING="executing",
DELIVERED="delivered",
CANCELLED="cancelled",
SUCCESS="success",
FAILED="failed",
}
--- Pickup and deploy set.
@ -177,14 +185,14 @@ _OPSTRANSPORTID=0
--- Army Group version.
-- @field #string version
OPSTRANSPORT.version="0.4.2"
OPSTRANSPORT.version="0.5.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Allow multiple pickup/depoly zones.
-- TODO: Stop/abort transport.
-- DONE: Allow multiple pickup/depoly zones.
-- DONE: Add start conditions.
-- DONE: Check carrier(s) dead.
@ -460,20 +468,6 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet, TransportZoneCombo)
-- Call iteravely for each group.
self:AddCargoGroups(group, TransportZoneCombo)
--[[
local cargo=self:_CreateCargoGroupData(group)
if cargo then
-- Add to main table.
table.insert(self.cargos, cargo)
self.Ncargo=self.Ncargo+1
-- Add to TZC table.
table.insert(TransportZoneCombo.Cargos, cargo)
TransportZoneCombo.Ncargo=TransportZoneCombo.Ncargo+1
end
]]
end
end

View File

@ -110,14 +110,16 @@ function PLATOON:AddWeaponRange(RangeMin, RangeMax, BitType)
self.weaponData[tostring(weapon.BitType)]=weapon
-- Debug info.
env.info(string.format("FF Adding weapon data: Bit=%s, Rmin=%d m, Rmax=%d m", tostring(weapon.BitType), weapon.RangeMin, weapon.RangeMax))
self:T(self.lid..string.format("Adding weapon data: Bit=%s, Rmin=%d m, Rmax=%d m", tostring(weapon.BitType), weapon.RangeMin, weapon.RangeMax))
if self.verbose>=2 then
local text="Weapon data:"
for _,_weapondata in pairs(self.weaponData) do
local weapondata=_weapondata
text=text..string.format("\n- Bit=%s, Rmin=%d m, Rmax=%d m", tostring(weapondata.BitType), weapondata.RangeMin, weapondata.RangeMax)
end
self:I(self.lid..text)
end
return self
end