diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 81d37269c..45419d5f9 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -288,6 +288,23 @@ function ZONE_BASE:GetCoordinate( Height ) --R2.1 return self.Coordinate end +--- Get 2D distance to a coordinate. +-- @param #ZONE_BASE self +-- @param Core.Point#COORDINATE Coordinate Reference coordinate. Can also be a DCS#Vec2 or DCS#Vec3 object. +-- @return #number Distance to the reference coordinate in meters. +function ZONE_BASE:Get2DDistance(Coordinate) + local a=self:GetVec2() + local b={} + if Coordinate.z then + b.x=Coordinate.x + b.y=Coordinate.z + else + b.x=Coordinate.x + b.y=Coordinate.y + end + local dist=UTILS.VecDist2D(a,b) + return dist +end --- Define a random @{DCS#Vec2} within the zone. -- @param #ZONE_BASE self diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index b63a1106e..b35e7cb83 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -115,6 +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 #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. @@ -476,6 +477,7 @@ OPSGROUP.version="0.7.5" -- TODO: Damage? -- TODO: Shot events? -- TODO: Marks to add waypoints/tasks on-the-fly. +-- DONE: A lot. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -687,7 +689,13 @@ function OPSGROUP:New(group) --- Triggers the FSM event "MissionCancel". -- @function [parent=#OPSGROUP] MissionCancel - -- @param #CHIEF self + -- @param #OPSGROUP self + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- Triggers the FSM event "MissionCancel" after a delay + -- @function [parent=#OPSGROUP] MissionCancel + -- @param #OPSGROUP self + -- @param #number delay Delay in seconds. -- @param Ops.Auftrag#AUFTRAG Mission The mission. --- On after "MissionCancel" event. @@ -1769,6 +1777,36 @@ function OPSGROUP:SetCarrierUnloaderPort(Length, Width) return self end +--- Check if group is currently inside a zone. +-- @param #OPSGROUP self +-- @param Core.Zone#ZONE Zone The zone. +-- @return #boolean If true, group is in this zone +function OPSGROUP:IsInZone(Zone) + local vec2=self:GetVec2() + local is=Zone:IsVec2InZone(vec2) + return is +end + +--- Get 2D distance to a coordinate. +-- @param #OPSGROUP self +-- @param Core.Point#COORDINATE Coordinate. Can also be a DCS#Vec2 or DCS#Vec3. +-- @return #number Distance in meters. +function OPSGROUP:Get2DDistance(Coordinate) + + local a=self:GetVec2() + local b={} + if Coordinate.z then + b.x=Coordinate.x + b.y=Coordinate.z + else + b.x=Coordinate.x + b.y=Coordinate.y + end + + local dist=UTILS.VecDist2D(a, b) + + return dist +end --- Check if this is a FLIGHTGROUP. -- @param #OPSGROUP self @@ -3567,7 +3605,7 @@ function OPSGROUP:_GetNextMission() -- Good example is the above transport. The legion should start the mission but the group should only start after the transport is finished. -- Check necessary conditions. - if mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.SCHEDULED and (mission:IsReadyToGo() or self.legion) and (mission.importance==nil or mission.importance<=vip) and transport then + if mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.SCHEDULED and (mission:IsReadyToGo() or self.legion) and (mission.importance==nil or mission.importance<=vip) and transport then return mission end @@ -4972,11 +5010,11 @@ function OPSGROUP:_UpdateLaser() end --- On before "ElementSpawned" event. Check that element is not in status spawned already. --- @param #FLIGHTGROUP self +-- @param #OPSGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Ops.OpsGroup#OPSGROUP.Element Element The flight group element. +-- @param #OPSGROUP.Element Element The flight group element. function OPSGROUP:onbeforeElementSpawned(From, Event, To, Element) if Element and Element.status==OPSGROUP.ElementStatus.SPAWNED then @@ -5505,10 +5543,19 @@ function OPSGROUP:_CheckCargoTransport() if self:IsNotCarrier() then -- Debug info. - self:T(self.lid.."Not carrier ==> pickup") + self:T(self.lid.."Not carrier ==> pickup?") + + -- Get transport zones. + local transportZone=self.cargoTransport:_GetPickupZone(self) + + if transportZone then - -- Initiate the cargo transport process. - self:__Pickup(-1) + -- Initiate the cargo transport process. + self:__Pickup(-1, transportZone) + + else + self:T(self.lid.."Not carrier ==> pickup") + end elseif self:IsPickingup() then @@ -5730,13 +5777,16 @@ function OPSGROUP:_GetNextCargoTransport() local function _sort(a, b) local transportA=a --Ops.OpsTransport#OPSTRANSPORT local transportB=b --Ops.OpsTransport#OPSTRANSPORT - local distA=transportA.pickupzone:GetCoordinate():Get2DDistance(coord) - local distB=transportB.pickupzone:GetCoordinate():Get2DDistance(coord) - return (transportA.prio Pickup") - self:Pickup(self.cargoTransport.pickupzone) + -- Pickup the next batch. + self:I(self.lid.."Unloaded: Still cargo left ==> Pickup") + self:Pickup(transportZone) + + else + env.info("FF error not implemented case!") + end else @@ -9920,6 +9967,22 @@ function OPSGROUP:_GetTemplate(Copy) return nil end +--- Clear waypoints. +-- @param #OPSGROUP self +-- @param #number IndexMin Clear waypoints up to this min WP index. Default 1. +-- @param #number IndexMax Clear waypoints up to this max WP index. Default `#self.waypoints`. +function OPSGROUP:ClearWaypoints(IndexMin, IndexMax) + + IndexMin=IndexMin or 1 + IndexMax=IndexMax or #self.waypoints + + -- Clear all waypoints. + for i=IndexMax,IndexMin,-1 do + table.remove(self.waypoints, i) + end + --self.waypoints={} +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index ce6887c25..5d0cd8b7e 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -38,11 +38,11 @@ -- @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 Core.Zone#ZONE unboardzone (Optional) Zone where the cargo is going to after disembarkment. -- @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. @@ -117,6 +117,7 @@ OPSTRANSPORT = { cargos = {}, carriers = {}, carrierTransportStatus = {}, + transportZones = {}, conditionStart = {}, pathsTransport = {}, pathsPickup = {}, @@ -141,13 +142,22 @@ OPSTRANSPORT.Status={ DELIVERED="delivered", } ---- Path. +--- Pickup and deploy set. +-- @type OPSTRANSPORT.TransportZone +-- @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. + +--- Path used for pickup or transport. -- @type OPSTRANSPORT.Path -- @field #table coords Table of coordinates. -- @field #number radius Radomization radius in meters. Default 0 m. -- @field #number altitude Altitude in feet AGL. Only for aircraft. ---- Generic mission condition. +--- Generic transport condition. -- @type OPSTRANSPORT.Condition -- @field #function func Callback function to check for a condition. Should return a #boolean. -- @field #table arg Optional arguments passed to the condition callback function. @@ -163,6 +173,7 @@ OPSTRANSPORT.version="0.3.0" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO: Allow multiple pickup/depoly zones. -- TODO: Stop/abort transport. -- DONE: Add start conditions. -- DONE: Check carrier(s) dead. @@ -295,6 +306,31 @@ 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. @@ -501,15 +537,18 @@ end --- Get (all) cargo @{Ops.OpsGroup#OPSGROUP}s. Optionally, only delivered or undelivered groups can be returned. -- @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. --- @return #table Cargo Ops groups. -function OPSTRANSPORT:GetCargoOpsGroups(Delivered) +-- @param Ops.OpsGroup#OPSGROUP Carrier (Optional) Only count cargo groups that fit into the given carrier group. Current cargo is not a factor. +-- @return #table Cargo Ops groups. Can be and empty table `{}`. +function OPSTRANSPORT:GetCargoOpsGroups(Delivered, Carrier) local opsgroups={} for _,_cargo in pairs(self.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - if Delivered==nil or cargo.delivered==Delivered then + if Delivered==nil or cargo.delivered==Delivered then if cargo.opsgroup and not (cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped()) then - table.insert(opsgroups, cargo.opsgroup) + if Carrier==nil or Carrier:CanCargo(cargo.opsgroup) then + table.insert(opsgroups, cargo.opsgroup) + end end end end @@ -816,24 +855,27 @@ function OPSTRANSPORT:IsReadyToGo() -- Current abs time. local Tnow=timer.getAbsTime() - -- Pickup and deploy zones must be set. - if not self.pickupzone then - text=text.."No, pickup zone not defined!" - return false + -- Pickup AND deploy zones must be set. + local gotzones=false + for _,_tz in pairs(self.transportZones) do + local tz=_tz --#OPSTRANSPORT.TransportZone + if tz.PickupZone and tz.DeployZone then + gotzones=true + break + end end - if not self.deployzone then - text=text.."No, deploy zone not defined!" - return false + if not gotzones then + text=text.."No, pickup/deploy zone combo not yet defined!" end - + -- Start time did not pass yet. - if self.Tstart and Tnowself.Tstop or false then + if self.Tstop and Tnow>self.Tstop then text=text.."Nope, stop time already passed!" self:T(text) return false @@ -914,13 +956,21 @@ function OPSTRANSPORT:onafterStatus(From, Event, To) if self.verbose>=1 then -- Info text. - local pickupname=self.pickupzone and self.pickupzone:GetName() or "Unknown" - local deployname=self.deployzone and self.deployzone:GetName() or "Unknown" - local text=string.format("%s [%s --> %s]: Ncargo=%d/%d, Ncarrier=%d/%d", fsmstate:upper(), pickupname, deployname, self.Ncargo, self.Ndelivered, #self.carriers, self.Ncarrier) + local text=string.format("%s: Ncargo=%d/%d, Ncarrier=%d/%d", fsmstate:upper(), self.Ncargo, self.Ndelivered, #self.carriers, self.Ncarrier) -- Info about cargo and carrier. if self.verbose>=2 then + for i,_tz in pairs(self.transportZones) do + local tz=_tz --#OPSTRANSPORT.TransportZone + text=text..string.format("\n[%d] %s --> %s", i, tz.PickupZone and tz.PickupZone:GetName() or "Unknown", tz.DeployZone and tz.DeployZone and tz.DeployZone:GetName() or "Unknown", tz.Ncarriers) + end + + end + + -- Info about cargo and carrier. + if self.verbose>=3 then + text=text..string.format("\nCargos:") for _,_cargo in pairs(self.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup @@ -1222,6 +1272,62 @@ function OPSTRANSPORT:_CreateCargoGroupData(group) return cargo end +--- Count how many cargo groups are inside a zone. +-- @param #OPSTRANSPORT self +-- @param Core.Zone#ZONE Zone The zone object. +-- @param #boolean Delivered If `true`, only delivered groups are returned. If `false` only undelivered groups are returned. If `nil`, all groups are returned. +-- @param Ops.OpsGroup#OPSGROUP Carrier (Optional) Only count cargo groups that fit into the given carrier group. Current cargo is not a factor. +-- @return #number Number of cargo groups. +function OPSTRANSPORT:_CountCargosInZone(Zone, Delivered, Carrier) + + local cargos=self:GetCargoOpsGroups(Delivered, Carrier) + + local N=0 + for _,_cargo in pairs(cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP + if cargo:IsInZone(Zone) then + N=N+1 + end + end + + 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. +-- @param #OPSTRANSPORT self +-- @param Ops.OpsGroup#OPSGROUP Carrier The carrier OPS group. +-- @return Core.Zone#ZONE Pickup zone or `#nil`. +function OPSTRANSPORT:_GetPickupZone(Carrier) + + env.info(string.format("FF GetPickupZone")) + local pickup=nil + + local distmin=nil + for i,_transportzone in pairs(self.transportZones) do + local tz=_transportzone --#OPSTRANSPORT.TransportZone + + -- Count cargos in pickup zone. + local ncargo=self:_CountCargosInZone(tz.PickupZone, false, Carrier) + + env.info(string.format("FF GetPickupZone i=%d, ncargo=%d", i, ncargo)) + + if ncargo>0 then + + local vec2=Carrier:GetVec2() + + local dist=tz.PickupZone:Get2DDistance(vec2) + + if distmin==nil or dist