--- **Ops** - Troop transport assignment for OPS groups. -- -- ## Main Features: -- -- * 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 -- -- === -- -- ## Example Missions: -- -- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20Transport). -- -- === -- -- ### Author: **funkyfranky** -- -- == -- -- @module Ops.OpsTransport -- @image OPS_OpsTransport.png --- OPSTRANSPORT class. -- @type OPSTRANSPORT -- @field #string ClassName Name of the class. -- @field #string lid Log ID. -- @field #number uid Unique ID of the transport. -- @field #number verbose Verbosity level. -- -- @field #number prio Priority of this transport. Should be a number between 0 (high prio) and 100 (low prio). -- @field #boolean urgent If true, transport is urgent. -- @field #number importance Importance of this transport. Smaller=higher. -- @field #number Tstart Start time in *abs.* seconds. -- @field #number Tstop Stop time in *abs.* seconds. Default `#nil` (never stops). -- @field #number duration Duration (`Tstop-Tstart`) of the transport in seconds. -- @field #table conditionStart Start conditions. -- -- @field #table cargos Cargos. Each element is a @{Ops.OpsGroup#OPSGROUP.CargoGroup}. -- @field #table carriers Carriers assigned for this transport. -- -- @field #table tzCombos Table of transport zone combos. Each element of the table is of type `#OPSTRANSPORT.TransportZoneCombo`. -- @field #number tzcCounter Running number of added transport zone combos. -- @field #OPSTRANSPORT.TransportZoneCombo tzcDefault Default transport zone combo. -- -- @field #number Ncargo Total number of cargo groups. -- @field #number Ncarrier Total number of assigned carriers. -- @field #number Ndelivered Total number of cargo groups delivered. -- -- @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 #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 --- *Victory is the beautiful, bright-colored flower. Transport is the stem without which it could never have blossomed.* -- Winston Churchill -- -- === -- -- # 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). -- -- **IMPORTANT NOTES** -- -- * Cargo groups are **not** split and distributed into different carrier *units*. That means that the whole cargo group **must fit** into one of the carrier units. -- * Cargo groups must be inside the pickup zones to be considered for loading. Groups not inside the pickup zone will not get the command to board. -- -- # Constructor -- -- A new cargo transport assignment is created with the @{#OPSTRANSPORT.New}() function -- -- local opstransport=OPSTRANSPORT:New(Cargo, PickupZone, DeployZone) -- -- Here `Cargo` is an object of the troops to be transported. This can be a GROUP, OPSGROUP, SET_GROUP or SET_OPSGROUP object. -- -- `PickupZone` is the zone where the troops are picked up by the transport carriers. **Note** that troops *must* be inside this zone to be considered for loading! -- -- `DeployZone` is the zone where the troops are transported to. -- -- ## Assign to Carrier(s) -- -- A transport can be assigned to one or multiple carrier OPSGROUPS with this @{Ops.OpsGroup#OPSGROUP.AddOpsTransport}() function -- -- myopsgroup:AddOpsTransport(opstransport) -- -- There is no restriction to the type of the carrier. It can be a ground group (e.g. an APC), a helicopter, an airplane or even a ship. -- -- You can also mix carrier types. For instance, you can assign the same transport to APCs and helicopters. Or to helicopters and airplanes. -- -- # Examples -- -- A carrier group is assigned to transport infantry troops from zone "Zone Kobuleti X" to zone "Zone Alpha". -- -- -- Carrier group. -- local carrier=ARMYGROUP:New("TPz Fuchs Group") -- -- -- Set of groups to transport. -- local infantryset=SET_GROUP:New():FilterPrefixes("Infantry Platoon Alpha"):FilterOnce() -- -- -- Cargo transport assignment. -- local opstransport=OPSTRANSPORT:New(infantryset, ZONE:New("Zone Kobuleti X"), ZONE:New("Zone Alpha")) -- -- -- Assign transport to carrier. -- carrier:AddOpsTransport(opstransport) -- -- -- @field #OPSTRANSPORT OPSTRANSPORT = { ClassName = "OPSTRANSPORT", verbose = 0, cargos = {}, carriers = {}, carrierTransportStatus = {}, tzCombos = {}, tzcCounter = 0, conditionStart = {}, assets = {}, legions = {}, statusLegion = {}, requestID = {}, } --- Cargo transport status. -- @type OPSTRANSPORT.Status -- @field #string PLANNED Planning state. -- @field #string QUEUED Queued state. -- @field #string REQUESTED Requested state. -- @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", REQUESTED="requested", SCHEDULED="scheduled", EXECUTING="executing", DELIVERED="delivered", CANCELLED="cancelled", SUCCESS="success", FAILED="failed", } --- Pickup and deploy set. -- @type OPSTRANSPORT.TransportZoneCombo -- @field #number uid Unique ID of the TZ combo. -- @field #number Ncarriers Number of carrier groups using this transport zone. -- @field #number Ncargo Number of cargos assigned. This is a running number and *not* decreased if cargo is delivered or dead. -- @field #table Cargos Cargo groups of the TZ combo. Each element is of type `Ops.OpsGroup#OPSGROUP.CargoGroup`. -- @field Core.Zone#ZONE PickupZone Pickup zone. -- @field Core.Zone#ZONE DeployZone Deploy zone. -- @field Core.Zone#ZONE EmbarkZone Embark zone if different from pickup zone. -- @field Core.Zone#ZONE DisembarkZone Zone where the troops are disembared to. -- @field Wrapper.Airbase#AIRBASE PickupAirbase Airbase for pickup. -- @field Wrapper.Airbase#AIRBASE DeployAirbase Airbase for deploy. -- @field #table PickupPaths Paths for pickup. -- @field #table TransportPaths Path for Transport. Each elment of the table is of type `#OPSTRANSPORT.Path`. -- @field #table RequiredCargos Required cargos. -- @field #table DisembarkCarriers Carriers where the cargo is directly disembarked to. -- @field #boolean disembarkActivation If true, troops are spawned in late activated state when disembarked from carrier. -- @field #boolean disembarkInUtero If true, troops are disembarked "in utero". --- Path used for pickup or transport. -- @type OPSTRANSPORT.Path -- @field #table coords Table of coordinates. -- @field #number radius Radomization radius in meters. Default 0 m. -- @field #number altitude Altitude in feet AGL. Only for aircraft. --- Generic transport condition. -- @type OPSTRANSPORT.Condition -- @field #function func Callback function to check for a condition. Should return a #boolean. -- @field #table arg Optional arguments passed to the condition callback function. --- Transport ID. _OPSTRANSPORTID=0 --- Army Group version. -- @field #string version OPSTRANSPORT.version="0.5.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Stop/abort transport. -- DONE: Allow multiple pickup/depoly zones. -- DONE: Add start conditions. -- DONE: Check carrier(s) dead. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Create a new OPSTRANSPORT class object. Essential input are the troops that should be transported and the zones where the troops are picked up and deployed. -- @param #OPSTRANSPORT self -- @param Core.Set#SET_GROUP CargoGroups Groups to be transported as cargo. Can also be a single @{Wrapper.Group#GROUP} or @{Ops.OpsGroup#OPSGROUP} object. -- @param Core.Zone#ZONE PickupZone Pickup zone. This is the zone, where the carrier is going to pickup the cargo. **Important**: only cargo is considered, if it is in this zone when the carrier starts loading! -- @param Core.Zone#ZONE DeployZone Deploy zone. This is the zone, where the carrier is going to drop off the cargo. -- @return #OPSTRANSPORT self function OPSTRANSPORT:New(CargoGroups, PickupZone, DeployZone) -- Inherit everything from FSM class. local self=BASE:Inherit(self, FSM:New()) -- #OPSTRANSPORT -- Increase ID counter. _OPSTRANSPORTID=_OPSTRANSPORTID+1 -- Set some string id for output to DCS.log file. self.lid=string.format("OPSTRANSPORT [UID=%d] | ", _OPSTRANSPORTID) -- UID of this transport. self.uid=_OPSTRANSPORTID -- Defaults. self:SetPriority() self:SetTime() self:SetRequiredCarriers() -- Init arrays and counters. self.cargos={} self.carriers={} self.Ncargo=0 self.Ncarrier=0 self.Ndelivered=0 -- Set default TZC. self.tzcDefault=self:AddTransportZoneCombo(PickupZone, DeployZone, CargoGroups) -- FMS start state is PLANNED. self:SetStartState(OPSTRANSPORT.Status.PLANNED) -- PLANNED --> SCHEDULED --> EXECUTING --> DELIVERED self:AddTransition("*", "Planned", OPSTRANSPORT.Status.PLANNED) -- Cargo transport was planned. self:AddTransition(OPSTRANSPORT.Status.PLANNED, "Queued", OPSTRANSPORT.Status.QUEUED) -- Cargo is queued at at least one carrier. self:AddTransition(OPSTRANSPORT.Status.QUEUED, "Requested", OPSTRANSPORT.Status.REQUESTED) -- Transport assets have been requested from a warehouse. self:AddTransition(OPSTRANSPORT.Status.REQUESTED, "Scheduled", OPSTRANSPORT.Status.SCHEDULED) -- Cargo is queued at at least one carrier. self:AddTransition(OPSTRANSPORT.Status.PLANNED, "Scheduled", OPSTRANSPORT.Status.SCHEDULED) -- Cargo is queued at at least one carrier. self:AddTransition(OPSTRANSPORT.Status.SCHEDULED, "Executing", OPSTRANSPORT.Status.EXECUTING) -- Cargo is being transported. self:AddTransition("*", "Delivered", OPSTRANSPORT.Status.DELIVERED) -- Cargo was delivered. self:AddTransition("*", "Status", "*") self:AddTransition("*", "Stop", "*") self:AddTransition("*", "Cancel", OPSTRANSPORT.Status.CANCELLED) -- Command to cancel the transport. self:AddTransition("*", "Loaded", "*") self:AddTransition("*", "Unloaded", "*") self:AddTransition("*", "DeadCarrierUnit", "*") self:AddTransition("*", "DeadCarrierGroup", "*") self:AddTransition("*", "DeadCarrierAll", "*") ------------------------ --- Pseudo Functions --- ------------------------ --- Triggers the FSM event "Status". -- @function [parent=#OPSTRANSPORT] Status -- @param #OPSTRANSPORT self --- Triggers the FSM event "Status" after a delay. -- @function [parent=#OPSTRANSPORT] __Status -- @param #OPSTRANSPORT self -- @param #number delay Delay in seconds. --- Triggers the FSM event "Planned". -- @function [parent=#OPSTRANSPORT] Planned -- @param #OPSTRANSPORT self --- Triggers the FSM event "Planned" after a delay. -- @function [parent=#OPSTRANSPORT] __Planned -- @param #OPSTRANSPORT self -- @param #number delay Delay in seconds. --- Triggers the FSM event "Queued". -- @function [parent=#OPSTRANSPORT] Queued -- @param #OPSTRANSPORT self --- Triggers the FSM event "Queued" after a delay. -- @function [parent=#OPSTRANSPORT] __Queued -- @param #OPSTRANSPORT self -- @param #number delay Delay in seconds. --- Triggers the FSM event "Requested". -- @function [parent=#OPSTRANSPORT] Requested -- @param #OPSTRANSPORT self --- Triggers the FSM event "Requested" after a delay. -- @function [parent=#OPSTRANSPORT] __Requested -- @param #OPSTRANSPORT self -- @param #number delay Delay in seconds. --- Triggers the FSM event "Scheduled". -- @function [parent=#OPSTRANSPORT] Scheduled -- @param #OPSTRANSPORT self --- Triggers the FSM event "Scheduled" after a delay. -- @function [parent=#OPSTRANSPORT] __Scheduled -- @param #OPSTRANSPORT self -- @param #number delay Delay in seconds. --- Triggers the FSM event "Executing". -- @function [parent=#OPSTRANSPORT] Executing -- @param #OPSTRANSPORT self --- Triggers the FSM event "Executing" after a delay. -- @function [parent=#OPSTRANSPORT] __Executing -- @param #OPSTRANSPORT self -- @param #number delay Delay in seconds. --- Triggers the FSM event "Delivered". -- @function [parent=#OPSTRANSPORT] Delivered -- @param #OPSTRANSPORT self --- Triggers the FSM event "Delivered" after a delay. -- @function [parent=#OPSTRANSPORT] __Delivered -- @param #OPSTRANSPORT self -- @param #number delay Delay in seconds. --- Triggers the FSM event "Cancel". -- @function [parent=#OPSTRANSPORT] Cancel -- @param #OPSTRANSPORT self -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. --- Triggers the FSM event "Cancel" after a delay. -- @function [parent=#OPSTRANSPORT] __Cancel -- @param #OPSTRANSPORT self -- @param #number delay Delay in seconds. -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. --- On after "Cancel" event. -- @function [parent=#OPSTRANSPORT] OnAfterCancel -- @param #OPSTRANSPORT self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. --- Triggers the FSM event "Loaded". -- @function [parent=#OPSTRANSPORT] Loaded -- @param #OPSTRANSPORT self -- @param Ops.OpsGroup#OPSGROUP OpsGroupCargo OPSGROUP that was loaded into a carrier. -- @param Ops.OpsGroup#OPSGROUP OpsGroupCarrier OPSGROUP that was loaded into a carrier. -- @param Ops.OpsGroup#OPSGROUP.Element CarrierElement Carrier element. --- Triggers the FSM event "Loaded" after a delay. -- @function [parent=#OPSTRANSPORT] __Loaded -- @param #OPSTRANSPORT self -- @param #number delay Delay in seconds. -- @param Ops.OpsGroup#OPSGROUP OpsGroupCargo OPSGROUP that was loaded into a carrier. -- @param Ops.OpsGroup#OPSGROUP OpsGroupCarrier OPSGROUP that was loaded into a carrier. -- @param Ops.OpsGroup#OPSGROUP.Element CarrierElement Carrier element. --- On after "Loaded" event. -- @function [parent=#OPSTRANSPORT] OnAfterLoaded -- @param #OPSTRANSPORT self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param Ops.OpsGroup#OPSGROUP OpsGroupCargo OPSGROUP that was loaded into a carrier. -- @param Ops.OpsGroup#OPSGROUP OpsGroupCarrier OPSGROUP that was loaded into a carrier. -- @param Ops.OpsGroup#OPSGROUP.Element CarrierElement Carrier element. --- Triggers the FSM event "Unloaded". -- @function [parent=#OPSTRANSPORT] Unloaded -- @param #OPSTRANSPORT self -- @param Ops.OpsGroup#OPSGROUP OpsGroupCargo Cargo OPSGROUP that was unloaded from a carrier. -- @param Ops.OpsGroup#OPSGROUP OpsGroupCarrier Carrier OPSGROUP that unloaded the cargo. --- Triggers the FSM event "Unloaded" after a delay. -- @function [parent=#OPSTRANSPORT] __Unloaded -- @param #OPSTRANSPORT self -- @param #number delay Delay in seconds. -- @param Ops.OpsGroup#OPSGROUP OpsGroupCargo Cargo OPSGROUP that was unloaded from a carrier. -- @param Ops.OpsGroup#OPSGROUP OpsGroupCarrier Carrier OPSGROUP that unloaded the cargo. --- On after "Unloaded" event. -- @function [parent=#OPSTRANSPORT] OnAfterUnloaded -- @param #OPSTRANSPORT self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param Ops.OpsGroup#OPSGROUP OpsGroupCargo Cargo OPSGROUP that was unloaded from a carrier. -- @param Ops.OpsGroup#OPSGROUP OpsGroupCarrier Carrier OPSGROUP that unloaded the cargo. --TODO: Psydofunctions -- Call status update. self:__Status(-1) return self end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- User Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Add pickup and deploy zone combination. -- @param #OPSTRANSPORT self -- @param Core.Zone#ZONE PickupZone Zone where the troops are picked up. -- @param Core.Zone#ZONE DeployZone Zone where the troops are picked up. -- @param Core.Set#SET_GROUP CargoGroups Groups to be transported as cargo. Can also be a single @{Wrapper.Group#GROUP} or @{Ops.OpsGroup#OPSGROUP} object. -- @return #OPSTRANSPORT.TransportZoneCombo Transport zone table. function OPSTRANSPORT:AddTransportZoneCombo(PickupZone, DeployZone, CargoGroups) -- Increase counter. self.tzcCounter=self.tzcCounter+1 local tzcombo={} --#OPSTRANSPORT.TransportZoneCombo -- Init. tzcombo.uid=self.tzcCounter tzcombo.Ncarriers=0 tzcombo.Ncargo=0 tzcombo.Cargos={} tzcombo.RequiredCargos={} tzcombo.DisembarkCarriers={} tzcombo.PickupPaths={} tzcombo.TransportPaths={} -- Set zones. self:SetPickupZone(PickupZone, tzcombo) self:SetDeployZone(DeployZone, tzcombo) self:SetEmbarkZone(nil, tzcombo) -- Add cargo groups (could also be added later). if CargoGroups then self:AddCargoGroups(CargoGroups, tzcombo) end -- Add to table. table.insert(self.tzCombos, tzcombo) return tzcombo end --- Add cargo groups to be transported. -- @param #OPSTRANSPORT self -- @param Core.Set#SET_GROUP GroupSet Set of groups to be transported. Can also be passed as a single GROUP or OPSGROUP object. -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return #OPSTRANSPORT self function OPSTRANSPORT:AddCargoGroups(GroupSet, TransportZoneCombo) -- Use default TZC if no transport zone combo is provided. TransportZoneCombo=TransportZoneCombo or self.tzcDefault -- Check type of GroupSet provided. if GroupSet:IsInstanceOf("GROUP") or GroupSet:IsInstanceOf("OPSGROUP") then -- We got a single GROUP or OPSGROUP object. local cargo=self:_CreateCargoGroupData(GroupSet, TransportZoneCombo) 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 else -- We got a SET_GROUP object. for _,group in pairs(GroupSet.Set) do -- Call iteravely for each group. self:AddCargoGroups(group, TransportZoneCombo) end end -- Debug info. if self.verbose>=1 then local text=string.format("Added cargo groups:") local Weight=0 for _,_cargo in pairs(self:GetCargos()) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup local weight=cargo.opsgroup:GetWeightTotal() Weight=Weight+weight text=text..string.format("\n- %s [%s] weight=%.1f kg", cargo.opsgroup:GetName(), cargo.opsgroup:GetState(), weight) end text=text..string.format("\nTOTAL: Ncargo=%d, Weight=%.1f kg", #self.cargos, Weight) self:I(self.lid..text) end return self end --- Set pickup zone. -- @param #OPSTRANSPORT self -- @param Core.Zone#ZONE PickupZone Zone where the troops are picked up. -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return #OPSTRANSPORT self function OPSTRANSPORT:SetPickupZone(PickupZone, TransportZoneCombo) -- Use default TZC if no transport zone combo is provided. TransportZoneCombo=TransportZoneCombo or self.tzcDefault TransportZoneCombo.PickupZone=PickupZone if PickupZone and PickupZone:IsInstanceOf("ZONE_AIRBASE") then TransportZoneCombo.PickupAirbase=PickupZone._.ZoneAirbase end return self end --- Get pickup zone. -- @param #OPSTRANSPORT self -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return Core.Zone#ZONE Zone where the troops are picked up. function OPSTRANSPORT:GetPickupZone(TransportZoneCombo) -- Use default TZC if no transport zone combo is provided. TransportZoneCombo=TransportZoneCombo or self.tzcDefault return TransportZoneCombo.PickupZone end --- Set deploy zone. -- @param #OPSTRANSPORT self -- @param Core.Zone#ZONE DeployZone Zone where the troops are deployed. -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return #OPSTRANSPORT self function OPSTRANSPORT:SetDeployZone(DeployZone, TransportZoneCombo) -- Use default TZC if no transport zone combo is provided. TransportZoneCombo=TransportZoneCombo or self.tzcDefault -- Set deploy zone. TransportZoneCombo.DeployZone=DeployZone -- Check if this is an airbase. if DeployZone and DeployZone:IsInstanceOf("ZONE_AIRBASE") then TransportZoneCombo.DeployAirbase=DeployZone._.ZoneAirbase end return self end --- Get deploy zone. -- @param #OPSTRANSPORT self -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return Core.Zone#ZONE Zone where the troops are deployed. function OPSTRANSPORT:GetDeployZone(TransportZoneCombo) -- Use default TZC if no transport zone combo is provided. TransportZoneCombo=TransportZoneCombo or self.tzcDefault return TransportZoneCombo.DeployZone end --- Set embark zone. -- @param #OPSTRANSPORT self -- @param Core.Zone#ZONE EmbarkZone Zone where the troops are embarked. -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return #OPSTRANSPORT self function OPSTRANSPORT:SetEmbarkZone(EmbarkZone, TransportZoneCombo) -- Use default TZC if no transport zone combo is provided. TransportZoneCombo=TransportZoneCombo or self.tzcDefault TransportZoneCombo.EmbarkZone=EmbarkZone or TransportZoneCombo.PickupZone return self end --- Get embark zone. -- @param #OPSTRANSPORT self -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return Core.Zone#ZONE Zone where the troops are embarked from. function OPSTRANSPORT:GetEmbarkZone(TransportZoneCombo) -- Use default TZC if no transport zone combo is provided. TransportZoneCombo=TransportZoneCombo or self.tzcDefault return TransportZoneCombo.EmbarkZone end --- Set disembark zone. -- @param #OPSTRANSPORT self -- @param Core.Zone#ZONE DisembarkZone Zone where the troops are disembarked. -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return #OPSTRANSPORT self function OPSTRANSPORT:SetDisembarkZone(DisembarkZone, TransportZoneCombo) -- Use default TZC if no transport zone combo is provided. TransportZoneCombo=TransportZoneCombo or self.tzcDefault TransportZoneCombo.DisembarkZone=DisembarkZone return self end --- Get disembark zone. -- @param #OPSTRANSPORT self -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return Core.Zone#ZONE Zone where the troops are disembarked to. function OPSTRANSPORT:GetDisembarkZone(TransportZoneCombo) -- Use default TZC if no transport zone combo is provided. TransportZoneCombo=TransportZoneCombo or self.tzcDefault return TransportZoneCombo.DisembarkZone end --- Set activation status of group when disembarked from transport carrier. -- @param #OPSTRANSPORT self -- @param #boolean Active If `true` or `nil`, group is activated when disembarked. If `false`, group is late activated and needs to be activated manually. -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return #OPSTRANSPORT self function OPSTRANSPORT:SetDisembarkActivation(Active, TransportZoneCombo) -- Use default TZC if no transport zone combo is provided. TransportZoneCombo=TransportZoneCombo or self.tzcDefault if Active==true or Active==nil then TransportZoneCombo.disembarkActivation=true else TransportZoneCombo.disembarkActivation=false end return self end --- Get disembark activation. -- @param #OPSTRANSPORT self -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return #boolean If `true`, groups are spawned in late activated state. function OPSTRANSPORT:GetDisembarkActivation(TransportZoneCombo) -- Use default TZC if no transport zone combo is provided. TransportZoneCombo=TransportZoneCombo or self.tzcDefault return TransportZoneCombo.disembarkActivation end --- Set transfer carrier(s). These are carrier groups, where the cargo is directly loaded into when disembarked. -- @param #OPSTRANSPORT self -- @param Core.Set#SET_GROUP Carriers Carrier set. Can also be passed as a #GROUP, #OPSGROUP or #SET_OPSGROUP object. -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return #OPSTRANSPORT self function OPSTRANSPORT:SetDisembarkCarriers(Carriers, TransportZoneCombo) -- Debug info. self:T(self.lid.."Setting transfer carriers!") -- Use default TZC if no transport zone combo is provided. TransportZoneCombo=TransportZoneCombo or self.tzcDefault if Carriers:IsInstanceOf("GROUP") or Carriers:IsInstanceOf("OPSGROUP") then local carrier=self:_GetOpsGroupFromObject(Carriers) if carrier then table.insert(TransportZoneCombo.DisembarkCarriers, carrier) end elseif Carriers:IsInstanceOf("SET_GROUP") or Carriers:IsInstanceOf("SET_OPSGROUP") then for _,object in pairs(Carriers:GetSet()) do local carrier=self:_GetOpsGroupFromObject(object) if carrier then table.insert(TransportZoneCombo.DisembarkCarriers, carrier) end end else self:E(self.lid.."ERROR: Carriers must be a GROUP, OPSGROUP, SET_GROUP or SET_OPSGROUP object!") end return self end --- Get transfer carrier(s). These are carrier groups, where the cargo is directly loaded into when disembarked. -- @param #OPSTRANSPORT self -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return #table Table of carriers. function OPSTRANSPORT:GetDisembarkCarriers(TransportZoneCombo) -- Use default TZC if no transport zone combo is provided. TransportZoneCombo=TransportZoneCombo or self.tzcDefault return TransportZoneCombo.DisembarkCarriers end --- Set if group remains *in utero* after disembarkment from carrier. Can be used to directly load the group into another carrier. Similar to disembark in late activated state. -- @param #OPSTRANSPORT self -- @param #boolean InUtero If `true` or `nil`, group remains *in utero* after disembarkment. -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return #OPSTRANSPORT self function OPSTRANSPORT:SetDisembarkInUtero(InUtero, TransportZoneCombo) -- Use default TZC if no transport zone combo is provided. TransportZoneCombo=TransportZoneCombo or self.tzcDefault if InUtero==true or InUtero==nil then TransportZoneCombo.disembarkInUtero=true else TransportZoneCombo.disembarkInUtero=false end return self end --- Get disembark in utero. -- @param #OPSTRANSPORT self -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return #boolean If `true`, groups stay in utero after disembarkment. function OPSTRANSPORT:GetDisembarkInUtero(TransportZoneCombo) -- Use default TZC if no transport zone combo is provided. TransportZoneCombo=TransportZoneCombo or self.tzcDefault return TransportZoneCombo.disembarkInUtero end --- Set required cargo. This is a list of cargo groups that need to be loaded before the **first** transport will start. -- @param #OPSTRANSPORT self -- @param Core.Set#SET_GROUP Cargos Required cargo set. Can also be passed as a #GROUP, #OPSGROUP or #SET_OPSGROUP object. -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return #OPSTRANSPORT self function OPSTRANSPORT:SetRequiredCargos(Cargos, TransportZoneCombo) -- Debug info. self:T(self.lid.."Setting required cargos!") -- Use default TZC if no transport zone combo is provided. TransportZoneCombo=TransportZoneCombo or self.tzcDefault -- Create table. TransportZoneCombo.RequiredCargos=TransportZoneCombo.RequiredCargos or {} if Cargos:IsInstanceOf("GROUP") or Cargos:IsInstanceOf("OPSGROUP") then local cargo=self:_GetOpsGroupFromObject(Cargos) if cargo then table.insert(TransportZoneCombo.RequiredCargos, cargo) end elseif Cargos:IsInstanceOf("SET_GROUP") or Cargos:IsInstanceOf("SET_OPSGROUP") then for _,object in pairs(Cargos:GetSet()) do local cargo=self:_GetOpsGroupFromObject(object) if cargo then table.insert(TransportZoneCombo.RequiredCargos, cargo) end end else self:E(self.lid.."ERROR: Required Cargos must be a GROUP, OPSGROUP, SET_GROUP or SET_OPSGROUP object!") end return self end --- Get required cargos. This is a list of cargo groups that need to be loaded before the **first** transport will start. -- @param #OPSTRANSPORT self -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return #table Table of required cargo ops groups. function OPSTRANSPORT:GetRequiredCargos(TransportZoneCombo) -- Use default TZC if no transport zone combo is provided. TransportZoneCombo=TransportZoneCombo or self.tzcDefault 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.nCarriersMax0 then -- Get a random path for transport. local path=pathsTransport[math.random(#pathsTransport)] --#OPSTRANSPORT.Path local coordinates={} for _,_coord in ipairs(path.coords) do local coord=_coord --Core.Point#COORDINATE -- Get random coordinate. local c=coord:GetRandomCoordinateInRadius(path.radius) table.insert(coordinates, c) end return coordinates end return nil end --- Add path used to go to the pickup zone. If multiple paths are defined, a random one is chosen. -- @param #OPSTRANSPORT self -- @param Wrapper.Group#GROUP PathGroup A (late activated) GROUP defining a transport path by their waypoints. -- @param #boolean Reversed If `true`, add waypoints of group in reversed order. -- @param #number Radius Randomization radius in meters. Default 0 m. -- @param #number Altitude Altitude in feet AGL. Only for aircraft. -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport Zone combo. -- @return #OPSTRANSPORT self function OPSTRANSPORT:AddPathPickup(PathGroup, Reversed, Radius, Altitude, TransportZoneCombo) -- Use default TZC if no transport zone combo is provided. TransportZoneCombo=TransportZoneCombo or self.tzcDefault local path={} --#OPSTRANSPORT.Path path.coords={} path.radius=Radius or 0 path.altitude=Altitude -- Get route points. local waypoints=PathGroup:GetTaskRoute() if Reversed then for i=#waypoints,1,-1 do local wp=waypoints[i] local coord=COORDINATE:New(wp.x, wp.alt, wp.y) table.insert(path.coords, coord) end else for i=1,#waypoints do local wp=waypoints[i] local coord=COORDINATE:New(wp.x, wp.alt, wp.y) table.insert(path.coords, coord) end end -- Add path. table.insert(TransportZoneCombo.PickupPaths, path) return self end --- Get a path for pickup. -- @param #OPSTRANSPORT self -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport Zone combo. -- @return #table The path of COORDINATEs. function OPSTRANSPORT:_GetPathPickup(TransportZoneCombo) -- Use default TZC if no transport zone combo is provided. TransportZoneCombo=TransportZoneCombo or self.tzcDefault local paths=TransportZoneCombo.PickupPaths if paths and #paths>0 then -- Get a random path for transport. local path=paths[math.random(#paths)] --#OPSTRANSPORT.Path local coordinates={} for _,_coord in ipairs(path.coords) do local coord=_coord --Core.Point#COORDINATE -- Get random coordinate. local c=coord:GetRandomCoordinateInRadius(path.radius) table.insert(coordinates, c) end return coordinates end return nil end --- Add a carrier assigned for this transport. -- @param #OPSTRANSPORT self -- @param Ops.OpsGroup#OPSGROUP CarrierGroup Carrier OPSGROUP. -- @param #string Status Carrier Status. -- @return #OPSTRANSPORT self function OPSTRANSPORT:SetCarrierTransportStatus(CarrierGroup, Status) self.carrierTransportStatus[CarrierGroup.groupname]=Status return self end --- Get carrier transport status. -- @param #OPSTRANSPORT self -- @param Ops.OpsGroup#OPSGROUP CarrierGroup Carrier OPSGROUP. -- @return #string Carrier status. function OPSTRANSPORT:GetCarrierTransportStatus(CarrierGroup) return self.carrierTransportStatus[CarrierGroup.groupname] end --- Get unique ID of the transport assignment. -- @param #OPSTRANSPORT self -- @return #number UID. function OPSTRANSPORT:GetUID() return self.uid end --- Get number of delivered cargo groups. -- @param #OPSTRANSPORT self -- @return #number Total number of delivered cargo groups. function OPSTRANSPORT:GetNcargoDelivered() return self.Ndelivered end --- Get number of cargo groups. -- @param #OPSTRANSPORT self -- @return #number Total number of cargo groups. function OPSTRANSPORT:GetNcargoTotal() return self.Ncargo end --- Get number of carrier groups assigned for this transport. -- @param #OPSTRANSPORT self -- @return #number Total number of carrier groups. function OPSTRANSPORT:GetNcarrier() return self.Ncarrier end --- Add asset to transport. -- @param #OPSTRANSPORT self -- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset to be added. -- @return #OPSTRANSPORT self function OPSTRANSPORT:AddAsset(Asset) -- Debug info self:T(self.lid..string.format("Adding asset \"%s\" to transport", tostring(Asset.spawngroupname))) -- Add asset to table. self.assets=self.assets or {} table.insert(self.assets, Asset) return self end --- Delete asset from mission. -- @param #OPSTRANSPORT self -- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset to be removed. -- @return #OPSTRANSPORT self function OPSTRANSPORT:DelAsset(Asset) for i,_asset in pairs(self.assets or {}) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem if asset.uid==Asset.uid then self:T(self.lid..string.format("Removing asset \"%s\" from transport", tostring(Asset.spawngroupname))) table.remove(self.assets, i) return self end end return self end --- Add LEGION to the transport. -- @param #OPSTRANSPORT self -- @param Ops.Legion#LEGION Legion The legion. -- @return #OPSTRANSPORT self function OPSTRANSPORT:AddLegion(Legion) -- Debug info. self:I(self.lid..string.format("Adding legion %s", Legion.alias)) -- Add legion to table. table.insert(self.legions, Legion) return self end --- Remove LEGION from transport. -- @param #OPSTRANSPORT self -- @param Ops.Legion#LEGION Legion The legion. -- @return #OPSTRANSPORT self function OPSTRANSPORT:RemoveLegion(Legion) -- Loop over legions for i=#self.legions,1,-1 do local legion=self.legions[i] --Ops.Legion#LEGION if legion.alias==Legion.alias then -- Debug info. self:I(self.lid..string.format("Removing legion %s", Legion.alias)) table.remove(self.legions, i) return self end end self:E(self.lid..string.format("ERROR: Legion %s not found and could not be removed!", Legion.alias)) return self end --- Check if an OPS group is assigned as carrier for this transport. -- @param #OPSTRANSPORT self -- @param Ops.OpsGroup#OPSGROUP CarrierGroup Potential carrier OPSGROUP. -- @return #boolean If true, group is an assigned carrier. function OPSTRANSPORT:IsCarrier(CarrierGroup) for _,_carrier in pairs(self.carriers) do local carrier=_carrier --Ops.OpsGroup#OPSGROUP if carrier.groupname==CarrierGroup.groupname then return true end end return false end --- Check if transport is ready to be started. -- * Start time passed. -- * Stop time did not pass already. -- * All start conditions are true. -- @param #OPSTRANSPORT self -- @return #boolean If true, mission can be started. function OPSTRANSPORT:IsReadyToGo() -- Debug text. local text=self.lid.."Is ReadyToGo? " -- Current abs time. local Tnow=timer.getAbsTime() -- Pickup AND deploy zones must be set. local gotzones=false for _,_tz in pairs(self.tzCombos) do local tz=_tz --#OPSTRANSPORT.TransportZoneCombo if tz.PickupZone and tz.DeployZone then gotzones=true break end end if not gotzones then text=text.."No, pickup/deploy zone combo not yet defined!" return false end -- Start time did not pass yet. if self.Tstart and Tnowself.Tstop then text=text.."Nope, stop time already passed!" self:T(text) return false end -- All start conditions true? local startme=self:EvalConditionsAll(self.conditionStart) -- Nope, not yet. if not startme then text=text..("No way, at least one start condition is not true!") self:T(text) return false end -- We're good to go! text=text.."Yes!" self:T(text) return true end --- Set LEGION transport status. -- @param #OPSTRANSPORT self -- @param Ops.Legion#LEGION Legion The legion. -- @param #string Status New status. -- @return #OPSTRANSPORT self function OPSTRANSPORT:SetLegionStatus(Legion, Status) -- Old status local status=self:GetLegionStatus(Legion) -- Debug info. self:I(self.lid..string.format("Setting LEGION %s to status %s-->%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() 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(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(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() 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() local is=self:is(OPSTRANSPORT.Status.EXECUTING) return is end --- Check if all cargo was delivered (or is dead). -- @param #OPSTRANSPORT self -- @param #number Nmin Number of groups that must be actually delivered (and are not dead). Default 0. -- @return #boolean If true, all possible cargo was delivered. function OPSTRANSPORT:IsDelivered(Nmin) local is=self:is(OPSTRANSPORT.Status.DELIVERED) Nmin=Nmin or 0 if Nmin>self.Ncargo then Nmin=self.Ncargo end if self.Ndelivered=1 then -- Info text. 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 for i,_tz in pairs(self.tzCombos) do local tz=_tz --#OPSTRANSPORT.TransportZoneCombo text=text..string.format("\n[%d] %s --> %s", i, tz.PickupZone and tz.PickupZone:GetName() or "Unknown", tz.DeployZone and tz.DeployZone and tz.DeployZone:GetName() or "Unknown", tz.Ncarriers) end end -- Info about cargo and carrier. if self.verbose>=3 then text=text..string.format("\nCargos:") for _,_cargo in pairs(self:GetCargos()) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup local carrier=cargo.opsgroup:_GetMyCarrierElement() local name=carrier and carrier.name or "none" local cstate=carrier and carrier.status or "N/A" text=text..string.format("\n- %s: %s [%s], weight=%d kg, carrier=%s [%s], delivered=%s [UID=%s]", cargo.opsgroup:GetName(), cargo.opsgroup.cargoStatus:upper(), cargo.opsgroup:GetState(), cargo.opsgroup:GetWeightTotal(), name, cstate, tostring(cargo.delivered), tostring(cargo.opsgroup.cargoTransportUID)) end text=text..string.format("\nCarriers:") for _,_carrier in pairs(self.carriers) do local carrier=_carrier --Ops.OpsGroup#OPSGROUP text=text..string.format("\n- %s: %s [%s], Cargo Bay [current/reserved/total]=%d/%d/%d kg [free %d/%d/%d kg]", carrier:GetName(), carrier.carrierStatus:upper(), carrier:GetState(), carrier:GetWeightCargo(nil, false), carrier:GetWeightCargo(), carrier:GetWeightCargoMax(), carrier:GetFreeCargobay(nil, false), carrier:GetFreeCargobay(), carrier:GetFreeCargobayMax()) end end self:I(self.lid..text) end -- Check if all cargo was delivered (or is dead). self:_CheckDelivered() -- Update status again. if not self:IsDelivered() then self:__Status(-30) end end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM Event Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- On after "Planned" event. -- @param #OPSTRANSPORT self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. function OPSTRANSPORT:onafterPlanned(From, Event, To) self:T(self.lid..string.format("New status: %s-->%s", From, To)) end --- On after "Scheduled" event. -- @param #OPSTRANSPORT self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. function OPSTRANSPORT:onafterScheduled(From, Event, To) self:T(self.lid..string.format("New status: %s-->%s", From, To)) end --- On after "Executing" event. -- @param #OPSTRANSPORT self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. function OPSTRANSPORT:onafterExecuting(From, Event, To) self:T(self.lid..string.format("New status: %s-->%s", From, To)) end --- On before "Delivered" event. -- @param #OPSTRANSPORT self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. function OPSTRANSPORT:onbeforeDelivered(From, Event, To) -- Check that we do not call delivered again. if From==OPSTRANSPORT.Status.DELIVERED then return false end return true end --- On after "Delivered" event. -- @param #OPSTRANSPORT self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. function OPSTRANSPORT:onafterDelivered(From, Event, To) self:T(self.lid..string.format("New status: %s-->%s", From, To)) -- Inform all assigned carriers that cargo was delivered. They can have this in the queue or are currently processing this transport. for _,_carrier in pairs(self.carriers) do local carrier=_carrier --Ops.OpsGroup#OPSGROUP if self:GetCarrierTransportStatus(carrier)~=OPSTRANSPORT.Status.DELIVERED then carrier:Delivered(self) end end end --- On after "Loaded" event. -- @param #OPSTRANSPORT self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param Ops.OpsGroup#OPSGROUP OpsGroupCargo OPSGROUP that was loaded into a carrier. -- @param Ops.OpsGroup#OPSGROUP OpsGroupCarrier OPSGROUP that was loaded into a carrier. -- @param Ops.OpsGroup#OPSGROUP.Element CarrierElement Carrier element. function OPSTRANSPORT:onafterLoaded(From, Event, To, OpsGroupCargo, OpsGroupCarrier, CarrierElement) self:I(self.lid..string.format("Loaded OPSGROUP %s into carrier %s", OpsGroupCargo:GetName(), tostring(CarrierElement.name))) end --- On after "Unloaded" event. -- @param #OPSTRANSPORT self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param Ops.OpsGroup#OPSGROUP OpsGroupCargo Cargo OPSGROUP that was unloaded from a carrier. -- @param Ops.OpsGroup#OPSGROUP OpsGroupCarrier Carrier OPSGROUP that unloaded the cargo. function OPSTRANSPORT:onafterUnloaded(From, Event, To, OpsGroupCargo, OpsGroupCarrier) self:I(self.lid..string.format("Unloaded OPSGROUP %s", OpsGroupCargo:GetName())) end --- On after "DeadCarrierGroup" event. -- @param #OPSTRANSPORT self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param Ops.OpsGroup#OPSGROUP OpsGroup Carrier OPSGROUP that is dead. function OPSTRANSPORT:onafterDeadCarrierGroup(From, Event, To, OpsGroup) self:I(self.lid..string.format("Carrier OPSGROUP %s dead!", OpsGroup:GetName())) -- Remove group from carrier list/table. self:_DelCarrier(OpsGroup) end --- On after "DeadCarrierAll" event. -- @param #OPSTRANSPORT self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. function OPSTRANSPORT:onafterDeadCarrierAll(From, Event, To) self:I(self.lid..string.format("ALL Carrier OPSGROUPs are dead! Setting stage to PLANNED if not all cargo was delivered.")) self:_CheckDelivered() if not self:IsDelivered() then self:Planned() end end --- On after "Cancel" event. -- @param #OPSTRANSPORT self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. function OPSTRANSPORT:onafterCancel(From, Event, To) -- Number of OPSGROUPS assigned and alive. local Ngroups = #self.carriers -- Debug info. self:I(self.lid..string.format("CANCELLING mission in status %s. Will wait for %d groups to report mission DONE before evaluation", self.status, Ngroups)) -- Time stamp. self.Tover=timer.getAbsTime() if self.chief then -- Debug info. self:T(self.lid..string.format("CHIEF will cancel the transport. Will wait for mission DONE before evaluation!")) -- CHIEF will cancel the transport. self.chief:TransportCancel(self) elseif self.commander then -- Debug info. self:T(self.lid..string.format("COMMANDER will cancel the transport. Will wait for transport DELIVERED before evaluation!")) -- COMMANDER will cancel the transport. self.commander:TransportCancel(self) elseif self.legions and #self.legions>0 then -- Loop over all LEGIONs. for _,_legion in pairs(self.legions or {}) do local legion=_legion --Ops.Legion#LEGION -- Debug info. self:T(self.lid..string.format("LEGION %s will cancel the transport. Will wait for transport DELIVERED before evaluation!", legion.alias)) -- Legion will cancel all flight missions and remove queued request from warehouse queue. legion:TransportCancel(self) end else -- Debug info. self:T(self.lid..string.format("No legion, commander or chief. Attached OPS groups will cancel the transport on their own. Will wait for transport DELIVERED before evaluation!")) -- Loop over all carrier groups. for _,_carrier in pairs(self:GetCarriers()) do local carrier=_carrier --Ops.OpsGroup#OPSGROUP carrier:TransportCancel(self) end end -- Special mission states. if self:IsPlanned() or self:IsQueued() or self:IsRequested() or Ngroups==0 then self:T(self.lid..string.format("Cancelled transport was in %s stage with %d carrier groups assigned and alive. Call it DELIVERED!", self.status, Ngroups)) self:Delivered() end end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Check if all cargo of this transport assignment was delivered. -- @param #OPSTRANSPORT self function OPSTRANSPORT:_CheckDelivered() -- First check that at least one cargo was added (as we allow to do that later). if self.Ncargo>0 then local done=true local dead=true for _,_cargo in pairs(self:GetCargos()) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup if cargo.delivered then -- This one is delivered. dead=false elseif cargo.opsgroup==nil then -- This one is nil?! dead=false elseif cargo.opsgroup:IsDestroyed() then -- This one was destroyed. elseif cargo.opsgroup:IsDead() then -- This one is dead. elseif cargo.opsgroup:IsStopped() then -- This one is stopped. dead=false else done=false --Someone is not done! dead=false end end if dead then self:I(self.lid.."All cargo DEAD ==> Delivered!") self:Delivered() elseif done then self:I(self.lid.."All cargo DONE ==> Delivered!") self:Delivered() end end end --- Check if all required cargos are loaded. -- @param #OPSTRANSPORT self -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return #boolean If true, all required cargos are loaded or there is no required cargo. function OPSTRANSPORT:_CheckRequiredCargos(TransportZoneCombo) -- Use default TZC if no transport zone combo is provided. TransportZoneCombo=TransportZoneCombo or self.tzcDefault local requiredCargos=TransportZoneCombo.RequiredCargos if requiredCargos==nil or #requiredCargos==0 then return true end local carrierNames=self:_GetCarrierNames() local gotit=true for _,_cargo in pairs(requiredCargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP if not cargo:IsLoaded(carrierNames) then return false end end return true end --- Check if all given condition are true. -- @param #OPSTRANSPORT self -- @param #table Conditions Table of conditions. -- @return #boolean If true, all conditions were true. Returns false if at least one condition returned false. function OPSTRANSPORT:EvalConditionsAll(Conditions) -- Any stop condition must be true. for _,_condition in pairs(Conditions or {}) do local condition=_condition --#OPSTRANSPORT.Condition -- Call function. local istrue=condition.func(unpack(condition.arg)) -- Any false will return false. if not istrue then return false end end -- All conditions were true. return true end --- Find transfer carrier element for cargo group. -- @param #OPSTRANSPORT self -- @param Ops.OpsGroup#OPSGROUP CargoGroup The cargo group that needs to be loaded into a carrier unit/element of the carrier group. -- @param Core.Zone#ZONE Zone (Optional) Zone where the carrier must be in. -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return Ops.OpsGroup#OPSGROUP.Element New carrier element for cargo or nil. -- @return Ops.OpsGroup#OPSGROUP New carrier group for cargo or nil. function OPSTRANSPORT:FindTransferCarrierForCargo(CargoGroup, Zone, TransportZoneCombo) -- Use default TZC if no transport zone combo is provided. TransportZoneCombo=TransportZoneCombo or self.tzcDefault local carrier=nil --Ops.OpsGroup#OPSGROUP.Element local carrierGroup=nil --Ops.OpsGroup#OPSGROUP --TODO: maybe sort the carriers wrt to largest free cargo bay. Or better smallest free cargo bay that can take the cargo group weight. for _,_carrier in pairs(TransportZoneCombo.DisembarkCarriers) do local carrierGroup=_carrier --Ops.OpsGroup#OPSGROUP -- First check if carrier is alive and loading cargo. if carrierGroup and carrierGroup:IsAlive() and (carrierGroup:IsLoading() or TransportZoneCombo.DeployAirbase) then -- Find an element of the group that has enough free space. carrier=carrierGroup:FindCarrierForCargo(CargoGroup) if carrier then if Zone==nil or Zone:IsVec2InZone(carrier.unit:GetVec2()) then return carrier, carrierGroup else self:T2(self.lid.."Got transfer carrier but carrier not in zone (yet)!") end else self:T2(self.lid.."No transfer carrier available!") end end end return nil, nil end --- Create a cargo group data structure. -- @param #OPSTRANSPORT self -- @param Wrapper.Group#GROUP group The GROUP or OPSGROUP object. -- @return Ops.OpsGroup#OPSGROUP.CargoGroup Cargo group data. -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. function OPSTRANSPORT:_CreateCargoGroupData(group, TransportZoneCombo) -- Get ops group. local opsgroup=self:_GetOpsGroupFromObject(group) -- First check that this group is not already contained in this TZC. for _,_cargo in pairs(TransportZoneCombo.Cargos or {}) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup if cargo.opsgroup.groupname==opsgroup.groupname then -- Group is already contained. return nil end end -- Create a new data item. local cargo={} --Ops.OpsGroup#OPSGROUP.CargoGroup cargo.opsgroup=opsgroup cargo.delivered=false cargo.status="Unknown" cargo.disembarkCarrierElement=nil cargo.disembarkCarrierGroup=nil cargo.tzcUID=TransportZoneCombo return cargo end --- Count how many cargo groups are inside a zone. -- @param #OPSTRANSPORT self -- @param Core.Zone#ZONE Zone The zone object. -- @param #boolean Delivered If `true`, only delivered groups are returned. If `false` only undelivered groups are returned. If `nil`, all groups are returned. -- @param Ops.OpsGroup#OPSGROUP Carrier (Optional) Only count cargo groups that fit into the given carrier group. Current cargo is not a factor. -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return #number Number of cargo groups. function OPSTRANSPORT:_CountCargosInZone(Zone, Delivered, Carrier, TransportZoneCombo) -- Get cargo ops groups. local cargos=self:GetCargoOpsGroups(Delivered, Carrier, TransportZoneCombo) local N=0 for _,_cargo in pairs(cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP -- We look for groups that are not cargo, in the zone or in utero. if cargo:IsNotCargo(true) and (cargo:IsInZone(Zone) or cargo:IsInUtero()) then N=N+1 end end return N end --- Get a transport zone combination (TZC) for a carrier group. The pickup zone will be a zone, where the most cargo groups are located that fit into the carrier. -- @param #OPSTRANSPORT self -- @param Ops.OpsGroup#OPSGROUP Carrier The carrier OPS group. -- @return Core.Zone#ZONE Pickup zone or `#nil`. function OPSTRANSPORT:_GetTransportZoneCombo(Carrier) --- Selection criteria -- * Distance: pickup zone should be as close as possible. -- * Ncargo: Number of cargo groups. Pickup, where there is most cargo. -- * Ncarrier: Number of carriers already "working" on this TZC. Would be better if not all carriers work on the same combo while others are ignored. -- Get carrier position. local vec2=Carrier:GetVec2() local pickup=nil --#OPSTRANSPORT.TransportZoneCombo local distmin=nil for i,_transportzone in pairs(self.tzCombos) do local tz=_transportzone --#OPSTRANSPORT.TransportZoneCombo -- 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)) -- 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