Compare commits

...

1 Commits

Author SHA1 Message Date
Frank 0beb6b2022 Warehouse & OpsTransport
**WAREHOUSE**
- Switched from AI_CARGO_DISPATCHER to OPSTRANSPORT

**OPSTRANSPORT**
- Fixed bugs
2026-02-07 22:34:36 +01:00
4 changed files with 71 additions and 216 deletions
+52 -204
View File
@@ -35,7 +35,6 @@
-- === -- ===
-- --
-- ### Author: **funkyfranky** -- ### Author: **funkyfranky**
-- ### Co-author: FlightControl (cargo dispatcher classes)
-- --
-- === -- ===
-- --
@@ -132,14 +131,7 @@
-- a reasonable degree in DCS at the moment and hence cannot be used yet. -- a reasonable degree in DCS at the moment and hence cannot be used yet.
-- --
-- Furthermore, ground assets can be transferred between warehouses by transport units. These are APCs, helicopters and airplanes. The transportation process is modeled -- Furthermore, ground assets can be transferred between warehouses by transport units. These are APCs, helicopters and airplanes. The transportation process is modeled
-- in a realistic way by using the corresponding cargo dispatcher classes, i.e. -- in a realistic way by using the @{Ops.OpsTransport#OPSTRANSPORT} class.
--
-- * @{AI.AI_Cargo_Dispatcher_APC#AI_DISPATCHER_APC}
-- * @{AI.AI_Cargo_Dispatcher_Helicopter#AI_DISPATCHER_HELICOPTER}
-- * @{AI.AI_Cargo_Dispatcher_Airplane#AI_DISPATCHER_AIRPLANE}
--
-- Depending on which cargo dispatcher is used (ground or airbore), similar considerations like in the self propelled case are necessary. Howver, note that
-- the dispatchers as of yet cannot use user defined off road paths for example since they are classes of their own and use a different routing logic.
-- --
-- === -- ===
-- --
@@ -230,18 +222,6 @@
-- of 630 kg. This is important as groups cannot be split between carrier units when transporting, i.e. the total weight of the whole group must be smaller than the -- of 630 kg. This is important as groups cannot be split between carrier units when transporting, i.e. the total weight of the whole group must be smaller than the
-- cargo bay of the transport carrier. -- cargo bay of the transport carrier.
-- --
-- ### Setting the Load Radius
-- Boading and loading of cargo into a carrier is modeled in a realistic fashion in the AI\_CARGO\DISPATCHER classes, which are used inernally by the WAREHOUSE class.
-- Meaning that troops (cargo) will board, i.e. run or drive to the carrier, and only once they are in close proximity to the transporter they will be loaded (disappear).
--
-- Unfortunately, there are some situations where problems can occur. For example, in DCS tanks have the strong tentendcy not to drive around obstacles but rather to roll over them.
-- I have seen cases where an aircraft of the same coalition as the tank was in its way and the tank drove right through the plane waiting on a parking spot and destroying it.
--
-- As a workaround it is possible to set a larger load radius so that the cargo units are despawned further away from the carrier via the optional **loadradius** parameter:
--
-- warehouseBatumi:AddAsset("Leopard 2", nil, nil, nil, nil, 250)
--
-- Adding the asset like this will cause the units to be loaded into the carrier already at a distance of 250 meters.
-- --
-- ### Setting the AI Skill -- ### Setting the AI Skill
-- --
@@ -486,7 +466,7 @@
-- and the road connection is less than 3 km. -- and the road connection is less than 3 km.
-- --
-- The user can set the road connection manually with the @{#WAREHOUSE.SetRoadConnection} function. This is only functional for self propelled assets at the moment -- The user can set the road connection manually with the @{#WAREHOUSE.SetRoadConnection} function. This is only functional for self propelled assets at the moment
-- and not if using the AI dispatcher classes since these have a different logic to find the route. -- and not if using the OPSTRANSPORT class since this has a different logic to find the route.
-- --
-- ## Off Road Connections -- ## Off Road Connections
-- --
@@ -595,7 +575,7 @@
-- --
-- ## Cargo Bay and Weight Limitations -- ## Cargo Bay and Weight Limitations
-- --
-- The transportation of cargo is handled by the AI\_Dispatcher classes. These take the cargo bay of a carrier and the weight of -- The transportation of cargo is handled by the `OPSTRANSPORT` class. This takes the cargo bay of a carrier and the weight of
-- the cargo into account so that a carrier can only load a realistic amount of cargo. -- the cargo into account so that a carrier can only load a realistic amount of cargo.
-- --
-- However, if troops are supposed to be transported between warehouses, there is one important limitations one has to keep in mind. -- However, if troops are supposed to be transported between warehouses, there is one important limitations one has to keep in mind.
@@ -1798,12 +1778,13 @@ _WAREHOUSEDB = {
--- Warehouse class version. --- Warehouse class version.
-- @field #string version -- @field #string version
WAREHOUSE.version="1.0.2a" WAREHOUSE.version="2.0.0"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Warehouse todo list. -- TODO: Warehouse todo list.
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- DONE: Switch from AI Dispatchers to OPSTRANSPORT
-- TODO: Add check if assets "on the move" are stationary. Can happen if ground units get stuck in buildings. If stationary auto complete transport by adding assets to request warehouse? Time? -- TODO: Add check if assets "on the move" are stationary. Can happen if ground units get stuck in buildings. If stationary auto complete transport by adding assets to request warehouse? Time?
-- TODO: Optimize findpathonroad. Do it only once (first time) and safe paths between warehouses similar to off-road paths. -- TODO: Optimize findpathonroad. Do it only once (first time) and safe paths between warehouses similar to off-road paths.
-- NOGO: Spawn assets only virtually, i.e. remove requested assets from stock but do NOT spawn them ==> Interface to A2A dispatcher! Maybe do a negative sign on asset number? -- NOGO: Spawn assets only virtually, i.e. remove requested assets from stock but do NOT spawn them ==> Interface to A2A dispatcher! Maybe do a negative sign on asset number?
@@ -3743,12 +3724,15 @@ function WAREHOUSE:_JobDone()
-- Check conditions for being back home. -- Check conditions for being back home.
local ishome=false local ishome=false
if category==Group.Category.GROUND or category==Group.Category.HELICOPTER then if category==Group.Category.GROUND then
-- Units go back to the spawn zone, helicopters land and they should not move any more. -- Ground units go back to the spawn zone and they should not move any more.
ishome=inspawnzone and onground and notmoving ishome=inspawnzone and notmoving
elseif category==Group.Category.AIRPLANE then elseif category==Group.Category.AIRPLANE then
-- Planes need to be on ground at their home airbase and should not move any more. -- Planes need to be on ground at their home airbase and should not move any more.
ishome=athomebase and onground and notmoving ishome=athomebase and onground and notmoving
elseif category==Group.Category.HELICOPTER then
-- Helicopters go back to their airbase or spawn zone and should not move any more.
ishome=(athomebase or inspawnzone) and onground and notmoving
end end
-- Debug text. -- Debug text.
@@ -4698,135 +4682,47 @@ function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet
-- Prepare cargo groups for transport -- Prepare cargo groups for transport
------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------
-- Board radius, i.e. when the cargo will begin to board the carrier -- TODO: set asset.weight for cargos
local _boardradius=500
if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then -------------------------
_boardradius=5000 -- Create OPSTRANSPORT --
elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then -------------------------
--_loadradius=1000
--_boardradius=nil
elseif Request.transporttype==WAREHOUSE.TransportType.APC then
--_boardradius=nil
elseif Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER
or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then
_boardradius=6000
end
-- Empty cargo group set. -- OPSTRANSPORT
local CargoGroups=SET_CARGO:New() local CargoTransport --Ops.OpsTransport#OPSTRANSPORT
-- Add cargo groups to set.
for _,_group in pairs(CargoGroupSet:GetSetObjects()) do
-- Find asset belonging to this group.
local asset=self:FindAssetInDB(_group)
-- New cargo group object.
local cargogroup=CARGO_GROUP:New(_group, _cargotype,_group:GetName(),_boardradius, asset.loadradius)
-- Set weight for this group.
cargogroup:SetWeight(asset.weight)
-- Add group to group set.
CargoGroups:AddCargo(cargogroup)
end
------------------------
-- Create Dispatchers --
------------------------
-- Cargo dispatcher.
local CargoTransport --AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER
if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then
-- Pickup and deploy zones. CargoTransport = OPSTRANSPORT:New(CargoGroupSet, ZONE_AIRBASE:New(self.airbase:GetName()), ZONE_AIRBASE:New(Request.airbase:GetName()))
local PickupAirbaseSet = SET_ZONE:New():AddZone(ZONE_AIRBASE:New(self.airbase:GetName())) CargoTransport:SetEmbarkZone(self.spawnzone)
local DeployAirbaseSet = SET_ZONE:New():AddZone(ZONE_AIRBASE:New(Request.airbase:GetName())) CargoTransport:SetDisembarkZone(Request.warehouse.spawnzone)
-- Define dispatcher for this task.
CargoTransport = AI_CARGO_DISPATCHER_AIRPLANE:New(TransportGroupSet, CargoGroups, PickupAirbaseSet, DeployAirbaseSet)
-- Set home zone.
CargoTransport:SetHomeZone(ZONE_AIRBASE:New(self.airbase:GetName()))
elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then
-- Pickup and deploy zones. CargoTransport = OPSTRANSPORT:New(CargoGroupSet, self.spawnzone, Request.warehouse.spawnzone)
local PickupZoneSet = SET_ZONE:New():AddZone(self.spawnzone)
local DeployZoneSet = SET_ZONE:New():AddZone(Request.warehouse.spawnzone)
-- Define dispatcher for this task.
CargoTransport = AI_CARGO_DISPATCHER_HELICOPTER:New(TransportGroupSet, CargoGroups, PickupZoneSet, DeployZoneSet)
-- Home zone.
CargoTransport:SetHomeZone(self.spawnzone)
elseif Request.transporttype==WAREHOUSE.TransportType.APC then elseif Request.transporttype==WAREHOUSE.TransportType.APC then
-- Pickup and deploy zones. CargoTransport = OPSTRANSPORT:New(CargoGroupSet, self.spawnzone, Request.warehouse.spawnzone)
local PickupZoneSet = SET_ZONE:New():AddZone(self.spawnzone)
local DeployZoneSet = SET_ZONE:New():AddZone(Request.warehouse.spawnzone)
-- Define dispatcher for this task.
CargoTransport = AI_CARGO_DISPATCHER_APC:New(TransportGroupSet, CargoGroups, PickupZoneSet, DeployZoneSet, 0)
-- Set home zone.
CargoTransport:SetHomeZone(self.spawnzone)
elseif Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER elseif Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER
or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then
-- Pickup and deploy zones. CargoTransport = OPSTRANSPORT:New(CargoGroupSet, self.portzone, Request.warehouse.portzone)
local PickupZoneSet = SET_ZONE:New():AddZone(self.portzone) CargoTransport:SetEmbarkZone(self.spawnzone)
PickupZoneSet:AddZone(self.harborzone) CargoTransport:SetDisembarkZone(Request.warehouse.spawnzone)
local DeployZoneSet = SET_ZONE:New():AddZone(Request.warehouse.harborzone)
-- Get the shipping lane to use and pass it to the Dispatcher -- Get the shipping lane to use and pass it to the Dispatcher
local remotename = Request.warehouse.warehouse:GetName() local remotename = Request.warehouse.warehouse:GetName()
local ShippingLane = self.shippinglanes[remotename][math.random(#self.shippinglanes[remotename])] local ShippingLane = self.shippinglanes[remotename][math.random(#self.shippinglanes[remotename])]
-- Define dispatcher for this task. -- TODO: Add shipping lane
CargoTransport = AI_CARGO_DISPATCHER_SHIP:New(TransportGroupSet, CargoGroups, PickupZoneSet, DeployZoneSet, ShippingLane) -- CargoTransport:AddPathTransport(PathGroup)
-- Set home zone
CargoTransport:SetHomeZone(self.portzone)
else else
self:E(self.lid.."ERROR: Unknown transporttype!") self:E(self.lid.."ERROR: Unknown transporttype!")
end end
-- Set pickup and deploy radii.
-- The 20 m inner radius are to ensure that the helo does not land on the warehouse itself in the middle of the default spawn zone.
local pickupouter = 200
local pickupinner = 0
local deployouter = 200
local deployinner = 0
if Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER
or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then
pickupouter=1000
pickupinner=20
deployouter=1000
deployinner=0
else
pickupouter=200
pickupinner=0
if self.spawnzone.Radius~=nil then
pickupouter=self.spawnzone.Radius
pickupinner=20
end
deployouter=200
deployinner=0
if self.spawnzone.Radius~=nil then
deployouter=Request.warehouse.spawnzone.Radius
deployinner=20
end
end
CargoTransport:SetPickupRadius(pickupouter, pickupinner)
CargoTransport:SetDeployRadius(deployouter, deployinner)
-- Adjust carrier units. This has to come AFTER the dispatchers have been defined because they set the cargobay free weight! -- Adjust carrier units. This has to come AFTER the dispatchers have been defined because they set the cargobay free weight!
Request.carriercargo={} Request.carriercargo={}
@@ -4847,111 +4743,63 @@ function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet
end end
end end
-------------------------------- ----------------------------------
-- Dispatcher Event Functions -- -- Opstransport Event Functions --
-------------------------------- ----------------------------------
--- Function called after carrier picked up something. CargoTransport.warehouse = self
function CargoTransport:OnAfterPickedUp(From, Event, To, Carrier, PickupZone)
-- Get warehouse state.
local warehouse=Carrier:GetState(Carrier, "WAREHOUSE") --#WAREHOUSE
-- Debug message.
local text=string.format("Carrier group %s picked up at pickup zone %s.", Carrier:GetName(), PickupZone:GetName())
warehouse:T(warehouse.lid..text)
end
--- Function called if something was deployed.
function CargoTransport:OnAfterDeployed(From, Event, To, Carrier, DeployZone)
-- Get warehouse state.
local warehouse=Carrier:GetState(Carrier, "WAREHOUSE") --#WAREHOUSE
-- Debug message.
-- TODO: Depoloy zone is nil!
--local text=string.format("Carrier group %s deployed at deploy zone %s.", Carrier:GetName(), DeployZone:GetName())
--warehouse:T(warehouse.lid..text)
end
--- Function called if carrier group is going home.
function CargoTransport:OnAfterHome(From, Event, To, Carrier, Coordinate, Speed, Height, HomeZone)
-- Get warehouse state.
local warehouse=Carrier:GetState(Carrier, "WAREHOUSE") --#WAREHOUSE
-- Debug message.
local text=string.format("Carrier group %s going home to zone %s.", Carrier:GetName(), HomeZone:GetName())
warehouse:T(warehouse.lid..text)
end
--- Function called when a carrier unit has loaded a cargo group. --- Function called when a carrier unit has loaded a cargo group.
function CargoTransport:OnAfterLoaded(From, Event, To, Carrier, Cargo, CarrierUnit, PickupZone) function CargoTransport:OnAfterLoaded(From, Event, To, OpsGroupCargo, OpsGroupCarrier, CarrierElement)
-- Get warehouse state. -- Get warehouse state.
local warehouse=Carrier:GetState(Carrier, "WAREHOUSE") --#WAREHOUSE local warehouse=CargoTransport.warehouse --#WAREHOUSE
-- Debug message.
local text=string.format("Carrier group %s loaded cargo %s into unit %s in pickup zone %s", Carrier:GetName(), Cargo:GetName(), CarrierUnit:GetName(), PickupZone:GetName())
warehouse:T(warehouse.lid..text)
-- Get cargo group object. -- Get cargo group object.
local group=Cargo:GetObject() --Wrapper.Group#GROUP local group=OpsGroupCargo:GetGroup() --Cargo:GetObject() --Wrapper.Group#GROUP
-- Get request. -- Get request.
local request=warehouse:_GetRequestOfGroup(group, warehouse.pending) local request=warehouse:_GetRequestOfGroup(group, warehouse.pending)
-- Add cargo group to this carrier. -- Add cargo group to this carrier.
table.insert(request.carriercargo[CarrierUnit:GetName()], warehouse:_GetNameWithOut(Cargo:GetName())) table.insert(request.carriercargo[CarrierElement.name], warehouse:_GetNameWithOut(group:GetName()))
end end
--- Function called when cargo has arrived and was unloaded. --- Function called when cargo has arrived and was unloaded.
function CargoTransport:OnAfterUnloaded(From, Event, To, Carrier, Cargo, CarrierUnit, DeployZone) function CargoTransport:OnAfterUnloaded(From, Event, To, OpsGroupCargo, OpsGroupCarrier)
-- Get warehouse state. -- Get warehouse state.
local warehouse=Carrier:GetState(Carrier, "WAREHOUSE") --#WAREHOUSE local warehouse=CargoTransport.warehouse --Carrier:GetState(Carrier, "WAREHOUSE") --#WAREHOUSE
-- Get group obejet. -- Get group obejet.
local group=Cargo:GetObject() --Wrapper.Group#GROUP local group=OpsGroupCargo:GetGroup() --Cargo:GetObject() --Wrapper.Group#GROUP
-- Debug message. -- Debug message.
local text=string.format("Cargo group %s was unloaded from carrier unit %s.", tostring(group:GetName()), tostring(CarrierUnit:GetName())) local text=string.format("Cargo group %s was unloaded from carrier group %s.", tostring(group:GetName()), tostring(OpsGroupCarrier:GetName()))
warehouse:T(warehouse.lid..text) warehouse:T(warehouse.lid..text)
-- Load the cargo in the warehouse.
--Cargo:Load(warehouse.warehouse)
-- Trigger Arrived event. -- Trigger Arrived event.
warehouse:Arrived(group) warehouse:Arrived(group)
end end
--- On after BackHome event. -- TODO: Probably can also add some cargo/carrier dead functions here to simplify things at other places
function CargoTransport:OnAfterBackHome(From, Event, To, Carrier)
-- Intellisense.
local carrier=Carrier --Wrapper.Group#GROUP
-- Get warehouse state.
local warehouse=carrier:GetState(carrier, "WAREHOUSE") --#WAREHOUSE
carrier:SmokeWhite()
-- Debug info.
local text=string.format("Carrier %s is back home at warehouse %s.", tostring(Carrier:GetName()), tostring(warehouse.warehouse:GetName()))
MESSAGE:New(text, 5):ToAllIf(warehouse.Debug)
warehouse:I(warehouse.lid..text)
-- Call arrived event for carrier.
warehouse:__Arrived(1, Carrier)
-- Assign cargo to carriers
for _,carriergroup in pairs(TransportGroupSet:GetSetObjects()) do
local opsgroup=nil
if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE or Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then
opsgroup=FLIGHTGROUP:New(carriergroup)
elseif Request.transporttype==WAREHOUSE.TransportType.APC then
opsgroup=ARMYGROUP:New(carriergroup)
elseif Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER
or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then
opsgroup=NAVYGROUP:New(carriergroup)
end
opsgroup:AddOpsTransport(CargoTransport)
end end
-- Start dispatcher.
CargoTransport:__Start(5)
end end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+8 -3
View File
@@ -514,7 +514,7 @@ OPSGROUP.CargoStatus={
--- OpsGroup version. --- OpsGroup version.
-- @field #string version -- @field #string version
OPSGROUP.version="1.0.4" OPSGROUP.version="1.0.5"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list -- TODO list
@@ -5100,7 +5100,7 @@ function OPSGROUP:onafterTaskDone(From, Event, To, Task)
if Task.description=="Task_Land_At" then if Task.description=="Task_Land_At" then
self:T(self.lid.."Taske DONE Task_Land_At ==> Wait") self:T(self.lid.."Taske DONE Task_Land_At ==> Wait")
self:Cruise() -- After the land task, we set the helo to wait. This is because of an issue that the passing waypoint function is triggered immidiately if we do not do this!
self:Wait(20, 100) self:Wait(20, 100)
else else
self:T(self.lid.."Task Done but NO mission found ==> _CheckGroupDone in 1 sec") self:T(self.lid.."Task Done but NO mission found ==> _CheckGroupDone in 1 sec")
@@ -8331,7 +8331,8 @@ function OPSGROUP:_CheckCargoTransport()
end end
-- Boarding finished ==> Transport cargo. -- Boarding finished ==> Transport cargo.
if gotcargo and self.cargoTransport:_CheckRequiredCargos(self.cargoTZC, self) and not boarding then local required=self.cargoTransport:_CheckRequiredCargos(self.cargoTZC, self)
if gotcargo and required and not boarding then
self:T(self.lid.."Boarding/loading finished ==> Loaded") self:T(self.lid.."Boarding/loading finished ==> Loaded")
self.Tloading=nil self.Tloading=nil
self:LoadingDone() self:LoadingDone()
@@ -9968,6 +9969,10 @@ function OPSGROUP:onafterTransport(From, Event, To)
end end
end end
if self:IsWaiting() then
self:__Cruise(-10)
end
elseif self:IsArmygroup() then elseif self:IsArmygroup() then
-- Get transport path. -- Get transport path.
+4 -2
View File
@@ -244,7 +244,7 @@ _OPSTRANSPORTID=0
--- Army Group version. --- Army Group version.
-- @field #string version -- @field #string version
OPSTRANSPORT.version="0.8.0" OPSTRANSPORT.version="0.9.0"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list -- TODO list
@@ -2127,7 +2127,9 @@ function OPSTRANSPORT:_CheckRequiredCargos(TransportZoneCombo, CarrierGroup)
requiredCargos={} requiredCargos={}
for _,_cargo in pairs(TransportZoneCombo.Cargos) do for _,_cargo in pairs(TransportZoneCombo.Cargos) do
local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup
table.insert(requiredCargos, cargo.opsgroup) if not cargo.delivered then
table.insert(requiredCargos, cargo.opsgroup)
end
end end
end end