diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index c664f4f4b..3fa15d19a 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -6329,6 +6329,10 @@ function WAREHOUSE:_OnEventBirth(EventData) local request=self:GetRequestByID(rid) 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))) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index f4d8194a2..fc1761600 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -90,10 +90,7 @@ function ARMYGROUP:New(group) og:I(og.lid..string.format("WARNING: OPS group already exists in data base!")) return og end - - -- First set ARMYGROUP. - self.isArmygroup=true - + -- Inherit everything from FSM class. local self=BASE:Inherit(self, OPSGROUP:New(group)) -- #ARMYGROUP @@ -595,6 +592,16 @@ end -- @param #number Formation Formation of the group. function ARMYGROUP:onbeforeUpdateRoute(From, Event, To, n, Speed, Formation) if self:IsWaiting() then + self:E(self.lid.."Update route denied. Group is WAIRING!") + return false + elseif self:IsInUtero() then + self:E(self.lid.."Update route denied. Group is INUTERO!") + return false + elseif self:IsDead() then + self:E(self.lid.."Update route denied. Group is DEAD!") + return false + elseif self:IsStopped() then + self:E(self.lid.."Update route denied. Group is STOPPED!") return false end return true diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index d039dcc08..ea0a73eea 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -32,7 +32,7 @@ --- FLIGHTGROUP class. -- @type FLIGHTGROUP -- @field #string actype Type name of the aircraft. --- @field #number rangemax Max range in km. +-- @field #number rangemax Max range in meters. -- @field #number ceiling Max altitude the aircraft can fly at in meters. -- @field #number tankertype The refueling system type (0=boom, 1=probe), if the group is a tanker. -- @field #number refueltype The refueling system type (0=boom, 1=probe), if the group can refuel from a tanker. @@ -208,9 +208,6 @@ function FLIGHTGROUP:New(group) return og end - -- First set FLIGHTGROUP. - self.isFlightgroup=true - -- Inherit everything from FSM class. local self=BASE:Inherit(self, OPSGROUP:New(group)) -- #FLIGHTGROUP @@ -1766,14 +1763,12 @@ function FLIGHTGROUP:onafterCruise(From, Event, To) --- if self:IsTransporting() then - if self.cargoTransport and self.cargoTransport.deployzone and self.cargoTransport.deployzone:IsInstanceOf("ZONE_AIRBASE") then - local airbase=self.cargoTransport.deployzone:GetAirbase() - self:LandAtAirbase(airbase) + if self.cargoTransport and self.cargoTZC and self.cargoTZC.DeployAirbase then + self:LandAtAirbase(self.cargoTZC.DeployAirbase) end elseif self:IsPickingup() then - if self.cargoTransport and self.cargoTransport.pickupzone and self.cargoTransport.pickupzone:IsInstanceOf("ZONE_AIRBASE") then - local airbase=self.cargoTransport.pickupzone:GetAirbase() - self:LandAtAirbase(airbase) + if self.cargoTransport and self.cargoTZC and self.cargoTZC.PickupAirbase then + self:LandAtAirbase(self.cargoTZC.PickupAirbase) end else self:_CheckGroupDone(nil, 120) @@ -1983,6 +1978,9 @@ function FLIGHTGROUP:onbeforeUpdateRoute(From, Event, To, n) -- Group is dead! No more updates. self:E(self.lid.."Update route denied. Group is DEAD!") allowed=false + elseif self:IsInUtero() then + self:E(self.lid.."Update route denied. Group is INUTERO!") + allowed=false else -- Not airborne yet. Try again in 5 sec. self:T(self.lid.."Update route denied ==> checking back in 5 sec") @@ -2084,9 +2082,9 @@ function FLIGHTGROUP:onafterUpdateRoute(From, Event, To, n) local waypointAction=COORDINATE.WaypointAction.TurningPoint if self:IsLanded() or self:IsLandedAt() or self:IsAirborne()==false then -- Had some issues with passing waypoint function of the next WP called too ealy when the type is TurningPoint. Setting it to TakeOff solved it! - --waypointType=COORDINATE.WaypointType.TakeOff - waypointType=COORDINATE.WaypointType.TakeOffGroundHot - waypointAction=COORDINATE.WaypointAction.FromGroundAreaHot + waypointType=COORDINATE.WaypointType.TakeOff + --waypointType=COORDINATE.WaypointType.TakeOffGroundHot + --waypointAction=COORDINATE.WaypointAction.FromGroundAreaHot end -- Set current waypoint or we get problem that the _PassingWaypoint function is triggered too early, i.e. right now and not when passing the next WP. @@ -2585,9 +2583,9 @@ function FLIGHTGROUP:onbeforeWait(From, Event, To, Duration, Altitude, Speed) -- Check for a current transport assignment. if self.cargoTransport and not self:IsLandedAt() then - self:I(self.lid..string.format("WARNING: Got current TRANSPORT assignment ==> WAIT event is suspended for 30 sec!")) - Tsuspend=-30 - allowed=false + --self:I(self.lid..string.format("WARNING: Got current TRANSPORT assignment ==> WAIT event is suspended for 30 sec!")) + --Tsuspend=-30 + --allowed=false end -- Call wait again. diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index e4414fd86..500642a4e 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -114,9 +114,6 @@ function NAVYGROUP:New(group) return og end - -- First set NAVYGROUP. - self.isNavygroup=true - -- Inherit everything from FSM class. local self=BASE:Inherit(self, OPSGROUP:New(group)) -- #NAVYGROUP @@ -467,6 +464,9 @@ function NAVYGROUP:Status(From, Event, To) -- Is group alive? local alive=self:IsAlive() + -- + local freepath=0 + if alive then --- @@ -485,7 +485,7 @@ function NAVYGROUP:Status(From, Event, To) self:_CheckTurning() local disttoWP=math.min(self:GetDistanceToWaypoint(), UTILS.NMToMeters(10)) - local freepath=disttoWP + freepath=disttoWP -- Only check if not currently turning. if not self:IsTurning() then @@ -713,6 +713,16 @@ end -- @param #number Depth Depth in meters to the next waypoint. function NAVYGROUP:onbeforeUpdateRoute(From, Event, To, n, Speed, Depth) if self:IsWaiting() then + self:E(self.lid.."Update route denied. Group is WAIRING!") + return false + elseif self:IsInUtero() then + self:E(self.lid.."Update route denied. Group is INUTERO!") + return false + elseif self:IsDead() then + self:E(self.lid.."Update route denied. Group is DEAD!") + return false + elseif self:IsStopped() then + self:E(self.lid.."Update route denied. Group is STOPPED!") return false end return true @@ -771,7 +781,7 @@ function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) table.insert(waypoints, 1, current) - if not self.passedfinalwp then + if self:IsEngaging() or not self.passedfinalwp then -- Debug info. self:T(self.lid..string.format("Updateing route: WP %d-->%d (%d/%d), Speed=%.1f knots, Depth=%d m", self.currentwp, n, #waypoints, #self.waypoints, UTILS.MpsToKnots(self.speedWp), wp.alt)) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index b35e7cb83..a1db9890f 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -115,7 +115,7 @@ -- @field #table cargoqueue Table containing cargo groups to be transported. -- @field #table cargoBay Table containing OPSGROUP loaded into this group. -- @field Ops.OpsTransport#OPSTRANSPORT cargoTransport Current cargo transport assignment. --- @field Ops.OpsTransport#OPSTRANSPORT.TransportZone transportZone Transport zones (pickup, deploy etc.). +-- @field Ops.OpsTransport#OPSTRANSPORT.TransportZoneCombo cargoTZC Transport zone combo (pickup, deploy etc.) currently used. -- @field #string cargoStatus Cargo status of this group acting as cargo. -- @field #number cargoTransportUID Unique ID of the transport assignment this cargo group is associated with. -- @field #string carrierStatus Carrier status of this group acting as cargo carrier. @@ -512,6 +512,8 @@ function OPSGROUP:New(group) end end + self.group:IsAir() + -- Set the template. self:_SetTemplate() @@ -519,7 +521,27 @@ function OPSGROUP:New(group) self.dcsgroup=self:GetDCSGroup() self.controller=self.dcsgroup:getController() + -- Category. + self.category=self.dcsgroup:getCategory() + if self.category==Group.Category.GROUND then + self.isArmygroup=true + elseif self.category==Group.Category.TRAIN then + self.isArmygroup=true + self.isTrain=true + elseif self.category==Group.Category.SHIP then + self.isNavygroup=true + -- TODO submarine + elseif self.category==Group.Category.AIRPLANE then + self.isFlightgroup=true + elseif self.category==Group.Category.HELICOPTER then + self.isFlightgroup=true + self.isHelo=true + else + + end + local units=self.group:GetUnits() + if units then local masterunit=units[1] --Wrapper.Unit#UNIT @@ -531,10 +553,8 @@ function OPSGROUP:New(group) if self:IsFlightgroup() then - self.rangemax=masterunit:GetRange() - - self.descriptors=masterunit:GetDesc() - + self.rangemax=self.descriptors.range and self.descriptors.range*1000 or 500*1000 + self.ceiling=self.descriptors.Hmax self.tankertype=select(2, masterunit:IsTanker()) @@ -1783,7 +1803,10 @@ end -- @return #boolean If true, group is in this zone function OPSGROUP:IsInZone(Zone) local vec2=self:GetVec2() - local is=Zone:IsVec2InZone(vec2) + local is=false + if vec2 then + is=Zone:IsVec2InZone(vec2) + end return is end @@ -2750,17 +2773,33 @@ function OPSGROUP:PushTask(DCSTask) return self end +--- Returns true if the DCS controller currently has a task. +-- @param #OPSGROUP self +-- @param #number Delay Delay in seconds. +-- @return #boolean True or false if the controller has a task. Nil if no controller. +function OPSGROUP:HasTaskController(Delay) + local hastask=nil + if self.controller then + hastask=self.controller:hasTask() + end + self:I(self.lid..string.format("Controller hasTask=%s", tostring(hastask))) + return hastask +end + --- Clear DCS tasks. -- @param #OPSGROUP self -- @return #OPSGROUP self function OPSGROUP:ClearTasks() - if self:IsAlive() then + local hastask=self:HasTaskController() + if self:IsAlive() and self.controller and self:HasTaskController() then self:I(self.lid..string.format("CLEARING Tasks")) - self.group:ClearTasks() + self.controller:resetTask() end return self end + + --- Add a *scheduled* task. -- @param #OPSGROUP self -- @param #table task DCS task table structure. @@ -5262,7 +5301,11 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) self:T({Template=Template}) -- Spawn new group. - _DATABASE:Spawn(Template) + self.group=_DATABASE:Spawn(Template) + + -- Set DCS group and controller. + self.dcsgroup=self:GetDCSGroup() + self.controller=self.dcsgroup:getController() -- Set activation and controlled state. self.isLateActivated=Template.lateActivation @@ -5373,6 +5416,7 @@ function OPSGROUP:onafterDead(From, Event, To) -- No current cargo transport. self.cargoTransport=nil + self.cargoTZC=nil if self.Ndestroyed==#self.elements then if self.cohort then @@ -5488,10 +5532,11 @@ function OPSGROUP:_CheckCargoTransport() if self.verbose>=3 then local text="" for i,_transport in pairs(self.cargoqueue) do - local transport=_transport --#Ops.OpsTransport#OPSTRANSPORT - - local pickupname=transport.pickupzone and transport.pickupzone:GetName() or "unknown" - local deployname=transport.deployzone and transport.deployzone:GetName() or "unknown" + local transport=_transport --#Ops.OpsTransport#OPSTRANSPORT + local pickupzone=transport:GetPickupZone() + local deployzone=transport:GetDeployZone() + local pickupname=pickupzone and pickupzone:GetName() or "unknown" + local deployname=deployzone and deployzone:GetName() or "unknown" text=text..string.format("\n[%d] UID=%d Status=%s: %s --> %s", i, transport.uid, transport:GetState(), pickupname, deployname) for j,_cargo in pairs(transport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup @@ -5520,38 +5565,18 @@ function OPSGROUP:_CheckCargoTransport() -- Now handle the transport. if self.cargoTransport then - -- Debug info. - if self.verbose>=2 then - local pickupname=self.cargoTransport.pickupzone and self.cargoTransport.pickupzone:GetName() or "unknown" - local deployname=self.cargoTransport.deployzone and self.cargoTransport.deployzone:GetName() or "unknown" - local text=string.format("Carrier [%s]: %s --> %s", self.carrierStatus, pickupname, deployname) - for _,_cargo in pairs(self.cargoTransport.cargos) do - local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - local name=cargo.opsgroup:GetName() - local gstatus=cargo.opsgroup:GetState() - local cstatus=cargo.opsgroup.cargoStatus - local weight=cargo.opsgroup:GetWeightTotal() - local carriergroup, carrierelement, reserved=cargo.opsgroup:_GetMyCarrier() - local carrierGroupname=carriergroup and carriergroup.groupname or "none" - local carrierElementname=carrierelement and carrierelement.name or "none" - text=text..string.format("\n- %s (%.1f kg) [%s]: %s, carrier=%s (%s), delivered=%s", name, weight, gstatus, cstatus, carrierElementname, carrierGroupname, tostring(cargo.delivered)) - end - self:I(self.lid..text) - end - - if self:IsNotCarrier() then -- Debug info. self:T(self.lid.."Not carrier ==> pickup?") - -- Get transport zones. - local transportZone=self.cargoTransport:_GetPickupZone(self) + -- Get transport zone combo (TZC). + self.cargoTZC=self.cargoTransport:_GetTransportZoneCombo(self) - if transportZone then + if self.cargoTZC then -- Initiate the cargo transport process. - self:__Pickup(-1, transportZone) + self:__Pickup(-1) else self:T(self.lid.."Not carrier ==> pickup") @@ -5573,7 +5598,7 @@ function OPSGROUP:_CheckCargoTransport() local boarding=false local gotcargo=false - for _,_cargo in pairs(self.cargoTransport.cargos) do + for _,_cargo in pairs(self.cargoTZC.Cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup -- Check if anyone is still boarding. @@ -5589,7 +5614,7 @@ function OPSGROUP:_CheckCargoTransport() end -- Boarding finished ==> Transport cargo. - if gotcargo and self.cargoTransport:_CheckRequiredCargos() and not boarding then + if gotcargo and self.cargoTransport:_CheckRequiredCargos(self.cargoTZC) and not boarding then self:T(self.lid.."Boarding finished ==> Loaded") self:Loaded() else @@ -5613,7 +5638,7 @@ function OPSGROUP:_CheckCargoTransport() self:T(self.lid.."Unloading ==> Checking if all cargo was delivered") local delivered=true - for _,_cargo in pairs(self.cargoTransport.cargos) do + for _,_cargo in pairs(self.cargoTZC.Cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup local carrierGroup=cargo.opsgroup:_GetMyCarrierGroup() @@ -5629,12 +5654,33 @@ function OPSGROUP:_CheckCargoTransport() -- Unloading finished ==> pickup next batch or call it a day. if delivered then self:T(self.lid.."Unloading finished ==> UnloadingDone") - self:UnloadingDone() + self:__UnloadingDone(10) else self:Unloading() end end + + -- Debug info. (At this point, we might not have a current cargo transport ==> hence the check) + if self.verbose>=2 and self.cargoTransport then + local pickupzone=self.cargoTransport:GetPickupZone(self.cargoTZC) + local deployzone=self.cargoTransport:GetDeployZone(self.cargoTZC) + local pickupname=pickupzone and pickupzone:GetName() or "unknown" + local deployname=deployzone and deployzone:GetName() or "unknown" + local text=string.format("Carrier [%s]: %s --> %s", self.carrierStatus, pickupname, deployname) + for _,_cargo in pairs(self.cargoTransport:GetCargos(self.cargoTZC)) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + local name=cargo.opsgroup:GetName() + local gstatus=cargo.opsgroup:GetState() + local cstatus=cargo.opsgroup.cargoStatus + local weight=cargo.opsgroup:GetWeightTotal() + local carriergroup, carrierelement, reserved=cargo.opsgroup:_GetMyCarrier() + local carrierGroupname=carriergroup and carriergroup.groupname or "none" + local carrierElementname=carrierelement and carrierelement.name or "none" + text=text..string.format("\n- %s (%.1f kg) [%s]: %s, carrier=%s (%s), delivered=%s", name, weight, gstatus, cstatus, carrierElementname, carrierGroupname, tostring(cargo.delivered)) + end + self:I(self.lid..text) + end end @@ -5851,28 +5897,33 @@ end function OPSGROUP:_CheckGoPickup(CargoTransport) local done=true - for _,_cargo in pairs(CargoTransport.cargos) do - local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - - if self:CanCargo(cargo.opsgroup) then - - if cargo.delivered then - -- This one is delivered. - elseif cargo.opsgroup==nil or cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped() then - -- This one is dead. - elseif cargo.opsgroup:IsLoaded(CargoTransport:_GetCarrierNames()) then - -- This one is loaded into a(nother) carrier. - else - done=false --Someone is not done! + + if CargoTransport then + + for _,_cargo in pairs(CargoTransport.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + + if self:CanCargo(cargo.opsgroup) then + + if cargo.delivered then + -- This one is delivered. + elseif cargo.opsgroup==nil or cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped() then + -- This one is dead. + elseif cargo.opsgroup:IsLoaded(CargoTransport:_GetCarrierNames()) then + -- This one is loaded into a(nother) carrier. + else + done=false --Someone is not done! + end + end - + end - + + -- Debug info. + self:T(self.lid..string.format("Cargotransport UID=%d Status=%s: delivered=%s", CargoTransport.uid, CargoTransport:GetState(), tostring(done))) + end - -- Debug info. - self:T(self.lid..string.format("Cargotransport UID=%d Status=%s: delivered=%s", CargoTransport.uid, CargoTransport:GetState(), tostring(done))) - return done end @@ -6067,7 +6118,7 @@ function OPSGROUP:GetWeightCargo(UnitName, IncludeReserved) end -- Debug info. - self:T2(self.lid..string.format("Unit=%s (reserved=%s): weight=%d, gewicht=%d", tostring(UnitName), tostring(IncludeReserved), weight, gewicht)) + self:T3(self.lid..string.format("Unit=%s (reserved=%s): weight=%d, gewicht=%d", tostring(UnitName), tostring(IncludeReserved), weight, gewicht)) -- Quick check. if IncludeReserved==false and gewicht~=weight then @@ -6281,29 +6332,29 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Ops.OpsTransport#OPSTRANSPORT.TransportZone TransportZone The transport zones data table. -function OPSGROUP:onafterPickup(From, Event, To, TransportZone) +function OPSGROUP:onafterPickup(From, Event, To) -- Set carrier status. self:_NewCarrierStatus(OPSGROUP.CarrierStatus.PICKUP) - self.transportZone=TransportZone + local TZC=self.cargoTZC -- Pickup zone. - local Zone=TransportZone.PickupZone + local Zone=TZC.PickupZone -- Check if already in the pickup zone. local inzone=self:IsInZone(Zone) - local airbasePickup=nil --Wrapper.Airbase#AIRBASE - if Zone and Zone:IsInstanceOf("ZONE_AIRBASE") then - airbasePickup=Zone:GetAirbase() - end + -- Pickup at an airbase. + local airbasePickup=TZC.PickupAirbase --Wrapper.Airbase#AIRBASE -- Check if group is already ready for loading. local ready4loading=false if self:IsArmygroup() or self:IsNavygroup() then + + -- Army and Navy groups just need to be inside the zone. ready4loading=inzone + else -- Aircraft is already parking at the pickup airbase. @@ -6315,6 +6366,7 @@ function OPSGROUP:onafterPickup(From, Event, To, TransportZone) end end + -- Ready for loading? if ready4loading then -- We are already in the pickup zone ==> wait and initiate loading. @@ -6350,16 +6402,14 @@ function OPSGROUP:onafterPickup(From, Event, To, TransportZone) -- Activate uncontrolled group. if self:IsParking() and self:IsUncontrolled() then - self:StartUncontrolled(1) + self:StartUncontrolled() end - else - - -- Order group to land at an airbase. - self:LandAtAirbase(airbasePickup) - end + -- Order group to land at an airbase. + self:__LandAtAirbase(-0.5, airbasePickup) + elseif self.isHelo then --- @@ -6375,10 +6425,10 @@ function OPSGROUP:onafterPickup(From, Event, To, TransportZone) local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid, self.altitudeCruise, false) ; waypoint.detour=1 else - self:E(self.lid.."ERROR: Carrier aircraft cannot land in Pickup zone! Specify a ZONE_AIRBASE as pickup zone") + self:E(self.lid.."ERROR: Transportcarrier aircraft cannot land in Pickup zone! Specify a ZONE_AIRBASE as pickup zone") end - elseif self.isNavygroup then + elseif self:IsNavygroup() then --- -- Navy Group @@ -6388,7 +6438,7 @@ function OPSGROUP:onafterPickup(From, Event, To, TransportZone) local uid=cwp and cwp.uid or nil -- Get a (random) pre-defined transport path. - local path=self.cargoTransport:_GetPathPickup() + local path=self.cargoTransport:_GetPathPickup(self.cargoTZC) if path then -- Loop over coordinates. @@ -6406,7 +6456,7 @@ function OPSGROUP:onafterPickup(From, Event, To, TransportZone) self:__Cruise(-2) - elseif self.isArmygroup then + elseif self:IsArmygroup() then --- -- Army Group @@ -6416,14 +6466,13 @@ function OPSGROUP:onafterPickup(From, Event, To, TransportZone) local uid=cwp and cwp.uid or nil -- Get a (random) pre-defined transport path. - local path=self.cargoTransport:_GetPathPickup() + local path=self.cargoTransport:_GetPathPickup(self.cargoTZC) if path then -- Loop over coordinates. for i,coordinate in pairs(path) do local waypoint=ARMYGROUP.AddWaypoint(self, coordinate, nil, uid) ; waypoint.temp=true uid=waypoint.uid - --coordinate:MarkToAll(string.format("Path i=%d, UID=%d", i, uid)) end end @@ -6455,8 +6504,11 @@ function OPSGROUP:onafterLoading(From, Event, To) --TODO: sort cargos wrt weight. + -- Cargo group table. + local cargos=self.cargoTZC.Cargos + -- Loop over all cargos. - for _,_cargo in pairs(self.cargoTransport.cargos) do + for _,_cargo in pairs(cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup -- Check that cargo weight is @@ -6467,8 +6519,7 @@ function OPSGROUP:onafterLoading(From, Event, To) if cargo.opsgroup:IsNotCargo(true) and not (cargo.opsgroup:IsPickingup() or cargo.opsgroup:IsLoading() or cargo.opsgroup:IsTransporting() or cargo.opsgroup:IsUnloading()) then -- Check if cargo is in embark/pickup zone. - --local inzone=self.cargoTransport.embarkzone:IsCoordinateInZone(cargo.opsgroup:GetCoordinate()) - local inzone=cargo.opsgroup:IsInZone(self.transportZone.EmbarkZone) + local inzone=cargo.opsgroup:IsInZone(self.cargoTZC.EmbarkZone) -- Cargo MUST be inside zone or it will not be loaded! if inzone then @@ -6486,12 +6537,12 @@ function OPSGROUP:onafterLoading(From, Event, To) else -- Debug info. - self:T(self.lid..string.format("Cannot board carrier! Group %s is NOT (yet) in zone %s", cargo.opsgroup:GetName(), self.cargoTransport.embarkzone:GetName())) + self:T(self.lid..string.format("Cannot board carrier! Group %s is NOT (yet) in zone %s", cargo.opsgroup:GetName(), self.cargoTZC.EmbarkZone:GetName())) end else -- Debug info. - self:T(self.lid..string.format("Cargo %s NOT in embark zone %s", cargo.opsgroup:GetName(), self.cargoTransport.embarkzone:GetName())) + self:T(self.lid..string.format("Cargo %s NOT in embark zone %s", cargo.opsgroup:GetName(), self.cargoTZC.EmbarkZone:GetName())) end end @@ -6653,15 +6704,13 @@ function OPSGROUP:onafterTransport(From, Event, To) --TODO: This is all very similar to the onafterPickup() function. Could make it general. -- Deploy zone. - local Zone=self.transportZone.DeployZone + local Zone=self.cargoTZC.DeployZone -- Check if already in deploy zone. local inzone=self:IsInZone(Zone) --Zone:IsCoordinateInZone(self:GetCoordinate()) - local airbaseDeploy=nil --Wrapper.Airbase#AIRBASE - if Zone and Zone:IsInstanceOf("ZONE_AIRBASE") then - airbaseDeploy=Zone:GetAirbase() - end + -- Deploy airbase (if any). + local airbaseDeploy=self.cargoTZC.DeployAirbase --Wrapper.Airbase#AIRBASE -- Check if group is already ready for loading. local ready2deploy=false @@ -6679,7 +6728,7 @@ function OPSGROUP:onafterTransport(From, Event, To) if inzone then - -- We are already in the pickup zone ==> wait and initiate unloading. + -- We are already in the deploy zone ==> wait and initiate unloading. if (self:IsArmygroup() or self:IsNavygroup()) and not self:IsHolding() then self:FullStop() end @@ -6693,6 +6742,7 @@ function OPSGROUP:onafterTransport(From, Event, To) local Coordinate=nil --Core.Point#COORDINATE if self.cargoTransport.carrierGroup and self.cargoTransport.carrierGroup:IsLoading() then + --TODO: What the heck is self.cargoTransport.carrierGroup and where is this set?! -- Coordinate of the new carrier. Coordinate=self.cargoTransport.carrierGroup:GetCoordinate() else @@ -6701,14 +6751,14 @@ function OPSGROUP:onafterTransport(From, Event, To) end -- Add waypoint. - if self.isFlightgroup then + if self:IsFlightgroup() then + if airbaseDeploy then + --- -- Deploy at airbase --- - - if airbaseDeploy then - + local airbaseCurrent=self.currbase if airbaseCurrent then @@ -6718,10 +6768,10 @@ function OPSGROUP:onafterTransport(From, Event, To) self:StartUncontrolled() end - else - -- Order group to land at an airbase. - self:LandAtAirbase(airbaseDeploy) end + + -- Order group to land at an airbase. + self:__LandAtAirbase(-0.1, airbaseDeploy) elseif self.isHelo then @@ -6735,28 +6785,26 @@ function OPSGROUP:onafterTransport(From, Event, To) -- Cancel landedAt task. This should trigger Cruise once airborne. if self:IsFlightgroup() and self:IsLandedAt() then local Task=self:GetTaskCurrent() - --if Task and Task.description=="Task_Landed_At" then - self:__TaskCancel(5, Task) - --end + self:__TaskCancel(5, Task) end else self:E(self.lid.."ERROR: Carrier aircraft cannot land in Deploy zone! Specify a ZONE_AIRBASE as deploy zone") end - elseif self.isArmygroup then + elseif self:IsArmygroup() then local cwp=self:GetWaypointCurrent() local uid=cwp and cwp.uid or nil - local path=self.cargoTransport:_GetPathTransport() + -- Get transport path. + local path=self.cargoTransport:_GetPathTransport(self.cargoTZC) if path then -- Loop over coordinates. for i,coordinate in pairs(path) do local waypoint=ARMYGROUP.AddWaypoint(self, coordinate, nil, uid) ; waypoint.temp=true uid=waypoint.uid - --coordinate:MarkToAll(string.format("Path i=%d, UID=%d", i, uid)) end end @@ -6766,20 +6814,19 @@ function OPSGROUP:onafterTransport(From, Event, To) -- Give cruise command. self:Cruise() - elseif self.isNavygroup then + elseif self:IsNavygroup() then local cwp=self:GetWaypointCurrent() local uid=cwp and cwp.uid or nil -- Get a (random) pre-defined transport path. - local path=self.cargoTransport:_GetPathTransport() + local path=self.cargoTransport:_GetPathTransport(self.cargoTZC) if path then -- Loop over coordinates. for i,coordinate in pairs(path) do local waypoint=NAVYGROUP.AddWaypoint(self, coordinate, nil, uid) ; waypoint.temp=true uid=waypoint.uid - --coordinate:MarkToAll(string.format("Path i=%d, UID=%d", i, uid)) end end @@ -6806,9 +6853,9 @@ function OPSGROUP:onafterUnloading(From, Event, To) self:_NewCarrierStatus(OPSGROUP.CarrierStatus.UNLOADING) -- Deploy zone. - local zone=self.cargoTransport.disembarkzone or self.cargoTransport.deployzone --Core.Zone#ZONE + local zone=self.cargoTZC.DisembarkZone or self.cargoTZC.DeployZone --Core.Zone#ZONE - for _,_cargo in pairs(self.cargoTransport.cargos) do + for _,_cargo in pairs(self.cargoTZC.Cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup -- Check that cargo is loaded into this group. @@ -6821,11 +6868,11 @@ function OPSGROUP:onafterUnloading(From, Event, To) local carrierGroup=nil - if self.cargoTransport.disembarkCarriers and #self.cargoTransport.disembarkCarriers>0 then + if self.cargoTZC.DisembarkCarriers and #self.cargoTZC.DisembarkCarriers>0 then needscarrier=true - carrier, carrierGroup=self.cargoTransport:FindTransferCarrierForCargo(cargo.opsgroup, zone) + carrier, carrierGroup=self.cargoTransport:FindTransferCarrierForCargo(cargo.opsgroup, zone, self.cargoTZC) --TODO: max unloading time if transfer carrier does not arrive in the zone. @@ -6863,7 +6910,7 @@ function OPSGROUP:onafterUnloading(From, Event, To) -- Delivered to deploy zone --- - if self.cargoTransport.disembarkInUtero then + if self.cargoTransport:GetDisembarkInUtero(self.cargoTZC) then -- Unload but keep "in utero" (no coordinate provided). self:Unload(cargo.opsgroup) @@ -6872,10 +6919,10 @@ function OPSGROUP:onafterUnloading(From, Event, To) local Coordinate=nil - if self.cargoTransport.disembarkzone then + if self.cargoTransport:GetDisembarkZone(self.cargoTZC) then -- Random coordinate in disembark zone. - Coordinate=self.cargoTransport.disembarkzone:GetRandomCoordinate() + Coordinate=self.cargoTransport:GetDisembarkZone(self.cargoTZC):GetRandomCoordinate() else @@ -6891,7 +6938,7 @@ function OPSGROUP:onafterUnloading(From, Event, To) local Heading=math.random(0,359) -- Unload to Coordinate. - self:Unload(cargo.opsgroup, Coordinate, self.cargoTransport.disembarkActivation, Heading) + self:Unload(cargo.opsgroup, Coordinate, self.cargoTransport:GetDisembarkActivation(self.cargoTZC), Heading) end @@ -7050,7 +7097,7 @@ function OPSGROUP:onafterUnloadingDone(From, Event, To) -- Cancel landedAt task. if self:IsFlightgroup() and self:IsLandedAt() then local Task=self:GetTaskCurrent() - self:TaskCancel(Task) + self:__TaskCancel(5, Task) end -- Check everything was delivered (or is dead). @@ -7058,16 +7105,22 @@ function OPSGROUP:onafterUnloadingDone(From, Event, To) if not delivered then - local transportZone=self.cargoTransport:_GetPathPickup() + self.cargoTZC=self.cargoTransport:_GetTransportZoneCombo(self) - if transportZone then + if self.cargoTZC then -- Pickup the next batch. self:I(self.lid.."Unloaded: Still cargo left ==> Pickup") - self:Pickup(transportZone) + self:Pickup() else - env.info("FF error not implemented case!") + + -- Debug info. + self:I(self.lid..string.format("WARNING: Not all cargo was delivered but could not get a transport zone combo ==> setting carrier state to NOT CARRIER")) + + -- This is not a carrier anymore. + self:_NewCarrierStatus(OPSGROUP.CarrierStatus.NOTCARRIER) + end else @@ -7128,6 +7181,7 @@ function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) -- No current transport any more. self.cargoTransport=nil + self.cargoTZC=nil end -- Remove cargo transport from cargo queue. @@ -7531,8 +7585,8 @@ end -- @param #OPSGROUP self function OPSGROUP:_CheckStuck() - -- Holding means we are not stuck. - if self:IsHolding() or self:Is("Rearming") then + -- Cases we are not stuck. + if self:IsHolding() or self:Is("Rearming") or self:IsWaiting() then return end @@ -7569,6 +7623,9 @@ function OPSGROUP:_CheckStuck() -- Debug warning. self:E(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected for %d sec", speed, ExpectedSpeed, holdtime)) + + -- Give cruise command again. + self:__Cruise(1) --TODO: Stuck event! @@ -7916,13 +7973,16 @@ function OPSGROUP:_InitWaypoints(WpIndexMin, WpIndexMax) -- Get home and destination airbases from waypoints. self.homebase=self.homebase or self:GetHomebaseFromWaypoints() - self.destbase=self.destbase or self:GetDestinationFromWaypoints() + local destbase=self:GetDestinationFromWaypoints() + self.destbase=self.destbase or destbase self.currbase=self:GetHomebaseFromWaypoints() -- Remove the landing waypoint. We use RTB for that. It makes adding new waypoints easier as we do not have to check if the last waypoint is the landing waypoint. - if self.destbase and #self.waypoints>1 then + if destbase and #self.waypoints>1 then table.remove(self.waypoints, #self.waypoints) - else + end + + if self.destbase==nil then self.destbase=self.homebase end @@ -8051,7 +8111,7 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) -- Get next waypoint. Tricky part is that if local wpnext=opsgroup:GetWaypointNext() - if wpnext and (opsgroup.currentwp<#opsgroup.waypoints or opsgroup.adinfinitum or wpistemp) then + if wpnext then --and (opsgroup.currentwp<#opsgroup.waypoints or opsgroup.adinfinitum or wpistemp) -- Debug info. opsgroup:T(opsgroup.lid..string.format("Next waypoint UID=%d index=%d", wpnext.uid, opsgroup:GetWaypointIndex(wpnext.uid))) @@ -8074,6 +8134,12 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) opsgroup:_PassedFinalWaypoint(true, "_PassingWaypoint No next Waypoint found") end + + -- Check if final waypoint was reached. + if opsgroup.currentwp==#opsgroup.waypoints and not opsgroup.adinfinitum then + -- Set passed final waypoint. + opsgroup:_PassedFinalWaypoint(true, "_PassingWaypoint currentwp==#waypoints and NOT adinfinitum") + end -- Trigger PassingWaypoint event. if waypoint.temp then @@ -8122,9 +8188,13 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) if opsgroup:IsFlightgroup() then -- Land at current pos and wait for 60 min max. - env.info("FF LandAt for Pickup") - --opsgroup:LandAt(opsgroup:GetCoordinate(), 60*60) - opsgroup:LandAt(opsgroup.cargoTransport.pickupzone:GetCoordinate(), 60*60) + local coordinate=nil + if opsgroup.cargoTZC then + coordinate=opsgroup.cargoTZC.PickupZone:GetCoordinate() + else + coordinate=opsgroup:GetCoordinate() + end + opsgroup:LandAt(coordinate, 60*60) else @@ -8136,11 +8206,16 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) elseif opsgroup:IsTransporting() then - if opsgroup.isFlightgroup then + if opsgroup:IsFlightgroup() then -- Land at current pos and wait for 60 min max. - env.info("FF LandAt for Transporting") - opsgroup:LandAt(opsgroup:GetCoordinate(), 60*60) + local coordinate=nil + if opsgroup.cargoTZC then + coordinate=opsgroup.cargoTZC.DeployZone:GetCoordinate() + else + coordinate=opsgroup:GetCoordinate() + end + opsgroup:LandAt(coordinate, 60*60) else -- Stop and unload. diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 5d0cd8b7e..cd9357650 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -29,8 +29,7 @@ -- @field #string lid Log ID. -- @field #number uid Unique ID of the transport. -- @field #number verbose Verbosity level. --- @field #table cargos Cargos. Each element is a @{Ops.OpsGroup#OPSGROUP.CargoGroup}. --- @field #table carriers Carriers assigned for this transport. +-- -- @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. @@ -38,22 +37,21 @@ -- @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 transportZones Table of transport zones. Each element of the table is of type `#OPSTRANSPORT.TransportZone`. --- @field Core.Zone#ZONE pickupzone Zone where the cargo is picked up. --- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. --- @field Core.Zone#ZONE embarkzone (Optional) Zone where the cargo is supposed to embark. Default is the pickup zone. --- @field Core.Zone#ZONE disembarkzone (Optional) Zone where the cargo is disembarked. Default is the deploy zone. --- @field #boolean disembarkActivation Activation setting when group is disembared from carrier. --- @field #boolean disembarkInUtero Do not spawn the group in any any state but leave it "*in utero*". For example, to directly load it into another carrier. --- @field #table disembarkCarriers Table of carriers to which the cargo is disembared. This is a direct transfer from the old to the new carrier. --- @field #table requiredCargos Table of cargo groups that must be loaded before the first transport is started. +-- +-- @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 #table pathsTransport Transport paths of `#OPSGROUP.Path`. --- @field #table pathsPickup Pickup paths of `#OPSGROUP.Path`. +-- -- @field Ops.Auftrag#AUFTRAG mission The mission attached to this transport. -- @field #table assets Warehouse assets assigned for this transport. +-- -- @extends Core.Fsm#FSM --- *Victory is the beautiful, bright-colored flower. Transport is the stem without which it could never have blossomed.* -- Winston Churchill @@ -113,15 +111,13 @@ -- @field #OPSTRANSPORT OPSTRANSPORT = { ClassName = "OPSTRANSPORT", - verbose = 0, - cargos = {}, - carriers = {}, + verbose = 0, + cargos = {}, + carriers = {}, carrierTransportStatus = {}, - transportZones = {}, + tzCombos = {}, + tzcCounter = 0, conditionStart = {}, - pathsTransport = {}, - pathsPickup = {}, - requiredCargos = {}, assets = {}, } @@ -143,13 +139,23 @@ OPSTRANSPORT.Status={ } --- Pickup and deploy set. --- @type OPSTRANSPORT.TransportZone +-- @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 #OPSTRANSPORT.Path PickupPath Path for pickup. --- @field #OPSTRANSPORT.Path TransportPath Path for Transport. --- @field #numberr Ncarriers Number of carrier groups using this transport 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 @@ -167,7 +173,7 @@ _OPSTRANSPORTID=0 --- Army Group version. -- @field #string version -OPSTRANSPORT.version="0.3.0" +OPSTRANSPORT.version="0.4.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -184,11 +190,11 @@ OPSTRANSPORT.version="0.3.0" --- 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 GroupSet Set of groups to be transported. 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. +-- @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(GroupSet, Pickupzone, Deployzone) +function OPSTRANSPORT:New(CargoGroups, PickupZone, DeployZone) -- Inherit everything from FSM class. local self=BASE:Inherit(self, FSM:New()) -- #OPSTRANSPORT @@ -201,26 +207,21 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) -- UID of this transport. self.uid=_OPSTRANSPORTID - + -- Defaults. - self:SetPickupZone(Pickupzone) - self:SetDeployZone(Deployzone) - self:SetEmbarkZone() -- Default is pickup zone. self.cargos={} self.carriers={} + self.Ncargo=0 self.Ncarrier=0 self.Ndelivered=0 self:SetPriority() self:SetTime() - - -- Add cargo groups (could also be added later). - if GroupSet then - self:AddCargoGroups(GroupSet) - end - + -- Set default TZC. + self.tzcDefault=self:AddTransportZoneCombo(PickupZone, DeployZone, CargoGroups) + -- FMS start state is PLANNED. self:SetStartState(OPSTRANSPORT.Status.PLANNED) @@ -255,11 +256,54 @@ 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) +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 @@ -268,8 +312,13 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet) local cargo=self:_CreateCargoGroupData(GroupSet) 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 @@ -281,8 +330,13 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet) 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 @@ -306,97 +360,174 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet) return self end ---- Add pickup and deploy zone combination. Optionally, embark and disembark zones can be specified. --- --- * The pickup zone is a zone where --- * bla --- --- @param #OPSTRANSPORT self --- @param Core.Zone#ZONE PickupZone Zone where the troops are picked up. --- @return #OPSTRANSPORT.TransportZone Transport zone table. -function OPSTRANSPORT:AddTransportZones(PickupZone, DeployZone, EmbarkZone, DisembarkZone, PickupPath, TransportPath) - - local transport={} --#OPSTRANSPORT.TransportZone - - transport.PickupZone=PickupZone - transport.DeployZone=DeployZone - transport.EmbarkZone=EmbarkZone or PickupZone - transport.DisembarkZone=DisembarkZone - transport.PickupPath=PickupPath - transport.TransportPath=TransportPath - transport.Ncarriers=0 - - table.insert(self.transportZones, transport) - - return transport -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) - self.pickupzone=PickupZone +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) - self.deployzone=DeployZone +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) - self.embarkzone=EmbarkZone or self.pickupzone +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) - self.disembarkzone=DisembarkZone +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) +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 - self.disembarkActivation=true + TransportZoneCombo.disembarkActivation=true else - self.disembarkActivation=false - end + 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) +function OPSTRANSPORT:SetDisembarkCarriers(Carriers, TransportZoneCombo) -- Debug info. self:T(self.lid.."Setting transfer carriers!") - - -- Create table. - self.disembarkCarriers=self.disembarkCarriers or {} + + -- 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(self.disembarkCarriers, carrier) + table.insert(TransportZoneCombo.DisembarkCarriers, carrier) end elseif Carriers:IsInstanceOf("SET_GROUP") or Carriers:IsInstanceOf("SET_OPSGROUP") then @@ -404,7 +535,7 @@ function OPSTRANSPORT:SetDisembarkCarriers(Carriers) for _,object in pairs(Carriers:GetSet()) do local carrier=self:_GetOpsGroupFromObject(object) if carrier then - table.insert(self.disembarkCarriers, carrier) + table.insert(TransportZoneCombo.DisembarkCarriers, carrier) end end @@ -415,38 +546,72 @@ function OPSTRANSPORT:SetDisembarkCarriers(Carriers) 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) +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 - self.disembarkInUtero=true + TransportZoneCombo.disembarkInUtero=true else - self.disembarkInUtero=false - end + 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) +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. - self.requiredCargos=self.requiredCargos or {} + TransportZoneCombo.RequiredCargos=TransportZoneCombo.RequiredCargos or {} if Cargos:IsInstanceOf("GROUP") or Cargos:IsInstanceOf("OPSGROUP") then local cargo=self:_GetOpsGroupFromObject(Cargos) if cargo then - table.insert(self.requiredCargos, cargo) + table.insert(TransportZoneCombo.RequiredCargos, cargo) end elseif Cargos:IsInstanceOf("SET_GROUP") or Cargos:IsInstanceOf("SET_OPSGROUP") then @@ -454,7 +619,7 @@ function OPSTRANSPORT:SetRequiredCargos(Cargos) for _,object in pairs(Cargos:GetSet()) do local cargo=self:_GetOpsGroupFromObject(object) if cargo then - table.insert(self.requiredCargos, cargo) + table.insert(TransportZoneCombo.RequiredCargos, cargo) end end @@ -465,6 +630,17 @@ function OPSTRANSPORT:SetRequiredCargos(Cargos) 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 --- Add a carrier assigned for this transport. @@ -538,11 +714,14 @@ end -- @param #OPSTRANSPORT self -- @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 #table Cargo Ops groups. Can be and empty table `{}`. -function OPSTRANSPORT:GetCargoOpsGroups(Delivered, Carrier) +function OPSTRANSPORT:GetCargoOpsGroups(Delivered, Carrier, TransportZoneCombo) + + local cargos=self:GetCargos(TransportZoneCombo) local opsgroups={} - for _,_cargo in pairs(self.cargos) do + for _,_cargo in pairs(cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup if Delivered==nil or cargo.delivered==Delivered then if cargo.opsgroup and not (cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped()) then @@ -556,13 +735,26 @@ function OPSTRANSPORT:GetCargoOpsGroups(Delivered, Carrier) return opsgroups end ---- Get carrier @{Ops.OpsGroup#OPSGROUP}s. +--- Get carriers. -- @param #OPSTRANSPORT self -- @return #table Carrier Ops groups. -function OPSTRANSPORT:GetCarrierOpsGroups() +function OPSTRANSPORT:GetCarriers() return self.carriers end +--- Get cargos. +-- @param #OPSTRANSPORT self +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. +-- @return #table Cargos. +function OPSTRANSPORT:GetCargos(TransportZoneCombo) + + if TransportZoneCombo then + return TransportZoneCombo.Cargos + else + return self.cargos + end + +end --- Set transport start and stop time. -- @param #OPSTRANSPORT self @@ -652,8 +844,12 @@ end -- @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:AddPathTransport(PathGroup, Reversed, Radius, Altitude) +function OPSTRANSPORT:AddPathTransport(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={} @@ -679,20 +875,26 @@ function OPSTRANSPORT:AddPathTransport(PathGroup, Reversed, Radius, Altitude) -- Add path. - table.insert(self.pathsTransport, path) + table.insert(TransportZoneCombo.TransportPaths, path) return self end --- Get a path for transportation. -- @param #OPSTRANSPORT self +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport Zone combo. -- @return #table The path of COORDINATEs. -function OPSTRANSPORT:_GetPathTransport() +function OPSTRANSPORT:_GetPathTransport(TransportZoneCombo) - if self.pathsTransport and #self.pathsTransport>0 then + -- Use default TZC if no transport zone combo is provided. + TransportZoneCombo=TransportZoneCombo or self.tzcDefault + + local pathsTransport=TransportZoneCombo.TransportPaths + + if pathsTransport and #pathsTransport>0 then -- Get a random path for transport. - local path=self.pathsTransport[math.random(#self.pathsTransport)] --#OPSTRANSPORT.Path + local path=pathsTransport[math.random(#pathsTransport)] --#OPSTRANSPORT.Path local coordinates={} @@ -718,8 +920,12 @@ end -- @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) +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={} @@ -744,20 +950,26 @@ function OPSTRANSPORT:AddPathPickup(PathGroup, Reversed, Radius, Altitude) end -- Add path. - table.insert(self.pathsPickup, 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() +function OPSTRANSPORT:_GetPathPickup(TransportZoneCombo) - if self.pathsPickup and #self.pathsPickup>0 then + -- 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=self.pathsPickup[math.random(#self.pathsPickup)] --#OPSTRANSPORT.Path + local path=paths[math.random(#paths)] --#OPSTRANSPORT.Path local coordinates={} @@ -857,8 +1069,8 @@ function OPSTRANSPORT:IsReadyToGo() -- Pickup AND deploy zones must be set. local gotzones=false - for _,_tz in pairs(self.transportZones) do - local tz=_tz --#OPSTRANSPORT.TransportZone + for _,_tz in pairs(self.tzCombos) do + local tz=_tz --#OPSTRANSPORT.TransportZoneCombo if tz.PickupZone and tz.DeployZone then gotzones=true break @@ -961,8 +1173,8 @@ function OPSTRANSPORT:onafterStatus(From, Event, To) -- Info about cargo and carrier. if self.verbose>=2 then - for i,_tz in pairs(self.transportZones) do - local tz=_tz --#OPSTRANSPORT.TransportZone + 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 @@ -1166,17 +1378,23 @@ 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() +function OPSTRANSPORT:_CheckRequiredCargos(TransportZoneCombo) + + -- Use default TZC if no transport zone combo is provided. + TransportZoneCombo=TransportZoneCombo or self.tzcDefault - if self.requiredCargos==nil or #self.requiredCargos==0 then + 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(self.requiredCargos) do + for _,_cargo in pairs(requiredCargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP @@ -1219,26 +1437,30 @@ end -- @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) +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(self.disembarkCarriers or {}) do + 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 self.deployzone:IsInstanceOf("ZONE_AIRBASE")) then + 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:IsCoordinateInZone(carrier.unit:GetCoordinate()) 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)!") @@ -1277,10 +1499,11 @@ end -- @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) +function OPSTRANSPORT:_CountCargosInZone(Zone, Delivered, Carrier, TransportZoneCombo) - local cargos=self:GetCargoOpsGroups(Delivered, Carrier) + local cargos=self:GetCargoOpsGroups(Delivered, Carrier, TransportZoneCombo) local N=0 for _,_cargo in pairs(cargos) do @@ -1293,37 +1516,52 @@ function OPSTRANSPORT:_CountCargosInZone(Zone, Delivered, Carrier) return N end ---- Get a pickup zone for a carrier group. This will be a zone, where the most cargo groups are located that fit into the carrier. +--- 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:_GetPickupZone(Carrier) +function OPSTRANSPORT:_GetTransportZoneCombo(Carrier) - env.info(string.format("FF GetPickupZone")) - local pickup=nil + --- 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.transportZones) do - local tz=_transportzone --#OPSTRANSPORT.TransportZone + for i,_transportzone in pairs(self.tzCombos) do + local tz=_transportzone --#OPSTRANSPORT.TransportZoneCombo - -- Count cargos in pickup zone. - local ncargo=self:_CountCargosInZone(tz.PickupZone, false, Carrier) + -- Check that pickup and deploy zones were defined. + if tz.PickupZone and tz.DeployZone and tz.EmbarkZone then - env.info(string.format("FF GetPickupZone i=%d, ncargo=%d", i, ncargo)) - - if ncargo>0 then - - local vec2=Carrier:GetVec2() + -- Count undelivered cargos in embark(!) zone that fit into the carrier. + local ncargo=self:_CountCargosInZone(tz.EmbarkZone, false, Carrier, tz) - local dist=tz.PickupZone:Get2DDistance(vec2) - - if distmin==nil or dist0 then + + local dist=tz.PickupZone:Get2DDistance(vec2) + + if distmin==nil or dist