diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 88d487622..9fd532dbd 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1057,24 +1057,48 @@ end --- Returns a random Vec2 location within the zone. -- @param #ZONE_RADIUS self --- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. --- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. +-- @param #number inner (Optional) Minimal distance from the center of the zone. Default is 0. +-- @param #number outer (Optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. +-- @param #table surfacetypes (Optional) Table of surface types. Can also be a single surface type. We will try max 1000 times to find the right type! -- @return DCS#Vec2 The random location within the zone. -function ZONE_RADIUS:GetRandomVec2( inner, outer ) - self:F( self.ZoneName, inner, outer ) +function ZONE_RADIUS:GetRandomVec2(inner, outer, surfacetypes) - local Point = {} local Vec2 = self:GetVec2() local _inner = inner or 0 local _outer = outer or self:GetRadius() + + if surfacetypes and type(surfacetypes)~="table" then + surfacetypes={surfacetypes} + end - local angle = math.random() * math.pi * 2; - Point.x = Vec2.x + math.cos( angle ) * math.random(_inner, _outer); - Point.y = Vec2.y + math.sin( angle ) * math.random(_inner, _outer); + local function _getpoint() + local point = {} + local angle = math.random() * math.pi * 2 + point.x = Vec2.x + math.cos(angle) * math.random(_inner, _outer) + point.y = Vec2.y + math.sin(angle) * math.random(_inner, _outer) + return point + end + + local function _checkSurface(point) + for _,sf in pairs(surfacetypes) do + if sf==land.getSurfaceType(point) then + return true + end + end + return false + end - self:T( { Point } ) + local point=_getpoint() - return Point + if surfacetypes then + local N=1 ; local Nmax=1000 + while _checkSurface(point)==false and N<=Nmax do + point=_getpoint() + N=N+1 + end + end + + return point end --- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. @@ -1126,15 +1150,15 @@ end --- Returns a @{Core.Point#COORDINATE} object reflecting a random 3D location within the zone. -- @param #ZONE_RADIUS self --- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. --- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. --- @return Core.Point#COORDINATE -function ZONE_RADIUS:GetRandomCoordinate( inner, outer ) - self:F( self.ZoneName, inner, outer ) +-- @param #number inner (Optional) Minimal distance from the center of the zone. Default is 0. +-- @param #number outer (Optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. +-- @param #table surfacetypes (Optional) Table of surface types. Can also be a single surface type. We will try max 1000 times to find the right type! +-- @return Core.Point#COORDINATE The random coordinate. +function ZONE_RADIUS:GetRandomCoordinate(inner, outer, surfacetypes) - local Coordinate = COORDINATE:NewFromVec2( self:GetRandomVec2(inner, outer) ) + local vec2=self:GetRandomVec2(inner, outer, surfacetypes) - self:T3( { Coordinate = Coordinate } ) + local Coordinate = COORDINATE:NewFromVec2(vec2) return Coordinate end diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 2cf8ab17a..0e062ef69 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -1176,6 +1176,8 @@ function ARMYGROUP:_InitGroup() text=text..string.format("Unit type = %s\n", self.actype) text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) + text=text..string.format("Weight = %.1f kg\n", self:GetWeightTotal()) + text=text..string.format("Cargo bay = %.1f kg\n", self:GetFreeCargobay()) text=text..string.format("Elements = %d\n", #self.elements) text=text..string.format("Waypoints = %d\n", #self.waypoints) text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 1c78a5144..f0743bb8c 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -358,6 +358,14 @@ function FLIGHTGROUP:SetAirwing(airwing) return self end +--- Set if aircraft is VTOL capable. Unfortunately, there is no DCS way to determine this via scripting. +-- @param #FLIGHTGROUP self +-- @return #FLIGHTGROUP self +function FLIGHTGROUP:SetVTOL() + self.isVTOL=true + return self +end + --- Get airwing the flight group belongs to. -- @param #FLIGHTGROUP self -- @return Ops.AirWing#AIRWING The AIRWING object. @@ -1674,13 +1682,17 @@ function FLIGHTGROUP:onafterAirborne(From, Event, To) if self.isAI then if self:IsTransporting() then - env.info("FF transporting land at airbase ") - local airbase=self.cargoTransport.deployzone:GetAirbase() - self:LandAtAirbase(airbase) + if self.cargoTransport and self.cargoTransport.deployzone and self.cargoTransport.deployzone:IsInstanceOf("ZONE_AIRBASE") then + env.info("FF transporting land at airbase ") + local airbase=self.cargoTransport.deployzone:GetAirbase() + self:LandAtAirbase(airbase) + end elseif self:IsPickingup() then - env.info("FF pickingup land at airbase ") - local airbase=self.cargoTransport.pickupzone:GetAirbase() - self:LandAtAirbase(airbase) + if self.cargoTransport and self.cargoTransport.pickupzone and self.cargoTransport.pickupzone:IsInstanceOf("ZONE_AIRBASE") then + env.info("FF pickingup land at airbase ") + local airbase=self.cargoTransport.pickupzone:GetAirbase() + self:LandAtAirbase(airbase) + end else self:_CheckGroupDone(1) end @@ -2919,12 +2931,14 @@ function FLIGHTGROUP:_InitGroup() self.refueltype=select(2, unit:IsRefuelable()) -- Debug info. - if self.verbose>=1 then + if self.verbose>=0 then local text=string.format("Initialized Flight Group %s:\n", self.groupname) text=text..string.format("Unit type = %s\n", self.actype) text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) text=text..string.format("Range max = %.1f km\n", self.rangemax/1000) text=text..string.format("Ceiling = %.1f feet\n", UTILS.MetersToFeet(self.ceiling)) + text=text..string.format("Weight = %.1f kg\n", self:GetWeightTotal()) + text=text..string.format("Cargo bay = %.1f kg\n", self:GetFreeCargobay()) text=text..string.format("Tanker type = %s\n", tostring(self.tankertype)) text=text..string.format("Refuel type = %s\n", tostring(self.refueltype)) text=text..string.format("AI = %s\n", tostring(self.isAI)) @@ -3295,6 +3309,7 @@ function FLIGHTGROUP:InitWaypoints() -- Get home and destination airbases from waypoints. self.homebase=self.homebase or self:GetHomebaseFromWaypoints() self.destbase=self.destbase or self:GetDestinationFromWaypoints() + 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 then diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 00b1c3de0..38e66f59c 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -677,7 +677,8 @@ function NAVYGROUP:onafterSpawned(From, Event, To) -- Set radio. if self.radioDefault then - self:SwitchRadio() + -- CAREFUL: This makes DCS crash for some ships like speed boats or Higgins boats! (On a respawn for example). Looks like the command SetFrequency is causing this. + --self:SwitchRadio() else self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, false) end @@ -1186,6 +1187,8 @@ function NAVYGROUP:_InitGroup() text=text..string.format("Unit type = %s\n", self.actype) text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) + text=text..string.format("Weight = %.1f kg\n", self:GetWeightTotal()) + text=text..string.format("Cargo bay = %.1f kg\n", self:GetFreeCargobay()) text=text..string.format("Elements = %d\n", #self.elements) text=text..string.format("Waypoints = %d\n", #self.waypoints) text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 8ef7b1815..3f9bd6cab 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -19,13 +19,14 @@ -- @field #string lid Class id string for output to DCS log file. -- @field #string groupname Name of the group. -- @field Wrapper.Group#GROUP group Group object. --- @field #table template Template of the group. +-- @field DCS#Template template Template table of the group. -- @field #boolean isLateActivated Is the group late activated. -- @field #boolean isUncontrolled Is the group uncontrolled. -- @field #boolean isFlightgroup Is a FLIGHTGROUP. -- @field #boolean isArmygroup Is an ARMYGROUP. -- @field #boolean isNavygroup Is a NAVYGROUP. -- @field #boolean isHelo If true, the is a helicopter group. +-- @field #boolean isVTOL If true, the is capable of Vertical TakeOff and Landing (VTOL). -- @field #table elements Table of elements, i.e. units of the group. -- @field #boolean isAI If true, group is purely AI. -- @field #boolean isAircraft If true, group is airplane or helicopter. @@ -108,6 +109,7 @@ -- @field #OPSGROUP.CargoTransport cargoTransport Current cargo transport assignment. -- @field #string cargoStatus Cargo status of this group acting as cargo. -- @field #string carrierStatus Carrier status of this group acting as cargo carrier. +-- @field #number cargocounter Running number of cargo UIDs. -- -- @extends Core.Fsm#FSM @@ -165,6 +167,7 @@ OPSGROUP = { weaponData = {}, cargoqueue = {}, cargoBay = {}, + cargocounter = 1, } @@ -499,6 +502,7 @@ function OPSGROUP:New(group) self:SetLaser(1688, true, false, 0.5) self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO self.carrierStatus=OPSGROUP.CarrierStatus.NOTCARRIER + self.cargocounter=1 -- Init task counter. self.taskcurrent=0 @@ -582,7 +586,8 @@ function OPSGROUP:New(group) self:AddTransition("*", "Transport", "*") -- Carrier is transporting cargo. self:AddTransition("*", "Deploy", "*") -- Carrier is dropping off cargo. self:AddTransition("*", "Unload", "*") -- Carrier unload a cargo group. - self:AddTransition("*", "Unloaded", "*") -- Carrier unloaded all its cargo. + self:AddTransition("*", "Unloaded", "*") -- Carrier unloaded all its current cargo. + self:AddTransition("*", "Delivered", "*") -- Carrier delivered ALL cargo of the transport assignment. ------------------------ --- Pseudo Functions --- @@ -1283,7 +1288,7 @@ function OPSGROUP:Despawn(Delay, NoEventRemoveUnit) -- Clear any task ==> makes DCS crash! --self.group:ClearTasks() - + -- Get all units. local units=self:GetDCSUnits() @@ -1292,11 +1297,12 @@ function OPSGROUP:Despawn(Delay, NoEventRemoveUnit) if unit then local name=unit:getName() if name then + -- Despawn the unit. self:DespawnUnit(name, 0, NoEventRemoveUnit) end end end - + end end @@ -3002,6 +3008,11 @@ end -- @return Ops.Auftrag#AUFTRAG Next mission or *nil*. function OPSGROUP:_GetNextMission() + -- Check if group is acting as carrier or cargo at the moment. + if self:IsTransporting() or self:IsPickingup() or self:IsLoading() or self:IsLoaded() then + return nil + end + -- Number of missions. local Nmissions=#self.missionqueue @@ -4376,7 +4387,7 @@ end --- Respawn the group. -- @param #OPSGROUP self -- @param #number Delay Delay in seconds before respawn happens. Default 0. --- @param #table Template (optional) The template of the Group retrieved with GROUP:GetTemplate(). If the template is not provided, the template will be retrieved of the group itself. +-- @param DCS#Template Template (optional) The template of the Group retrieved with GROUP:GetTemplate(). If the template is not provided, the template will be retrieved of the group itself. -- @param #boolean Reset Reset positions if TRUE. -- @return #OPSGROUP self function OPSGROUP:_Respawn(Delay, Template, Reset) @@ -4389,17 +4400,6 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) -- Given template or get old. Template=Template or UTILS.DeepCopy(self.template) - - -- Get correct heading. - local function _Heading(course) - local h - if course<=180 then - h=math.rad(course) - else - h=-math.rad(360-course) - end - return h - end if self:IsAlive() then @@ -4467,6 +4467,10 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) -- Spawn new group. _DATABASE:Spawn(Template) + -- Set activation and controlled state. + self.isLateActivated=Template.lateActivation + self.isUncontrolled=Template.uncontrolled + -- Reset events. --self:ResetEvents() @@ -4577,69 +4581,15 @@ function OPSGROUP:_CheckCargoTransport() end self:I(self.lid..text) - - if self.cargoTransport then - -- TODO: Check if this group can actually transport any cargo. - - local done=true - for _,_cargo in pairs(self.cargoTransport.cargos) do - local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - - 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. - else - done=false --Someone is not done! - end - - --TODO: check if cargo is too heavy for this carrier group ==> elseif - - end + -- Loop over cargo queue and check if everything was delivered. + for i=#self.cargoqueue,1,-1 do + local transport=self.cargoqueue[i] --#OPSGROUP.CargoTransport + self:_CheckDelivered(transport) + end - if done then - self.cargoTransport.status=OPSGROUP.TransportStatus.DELIVERED - end - - end - -- Check if there is anything in the queue. - if not self.cargoTransport then - - -- Current position. - local coord=self:GetCoordinate() - - -- Sort results table wrt prio and distance to pickup zone. - local function _sort(a, b) - local transportA=a --#OPSGROUP.CargoTransport - local transportB=b --#OPSGROUP.CargoTransport - local distA=transportA.pickupzone:GetCoordinate():Get2DDistance(coord) - local distB=transportB.pickupzone:GetCoordinate():Get2DDistance(coord) - return (transportA.prio=cargotransport.Tstart and cargotransport.status==OPSGROUP.TransportStatus.SCHEDULED and (cargotransport.importance==nil or cargotransport.importance<=vip) then - cargotransport.status=OPSGROUP.TransportStatus.EXECUTING - self.cargoTransport=cargotransport - break - end - - end - + if not self.cargoTransport then + self.cargoTransport=self:_GetNextCargoTransport() end -- Now handle the transport. @@ -4718,6 +4668,7 @@ function OPSGROUP:_CheckCargoTransport() elseif self:IsUnloading() then + -- Debug info. self:I(self.lid.."Unloading ==> Checking if all cargo was delivered") local delivered=true @@ -4731,7 +4682,7 @@ function OPSGROUP:_CheckCargoTransport() end - -- Boarding finished ==> Transport cargo. + -- Unloading finished ==> pickup next batch or call it a day. if delivered then self:I(self.lid.."Unloading finished ==> Unloaded") self:Unloaded() @@ -4746,6 +4697,83 @@ function OPSGROUP:_CheckCargoTransport() return self end +--- Get cargo transport from cargo queue. +-- @param #OPSGROUP self +-- @return #OPSGROUP.CargoTransport The next due cargo transport or `nil`. +function OPSGROUP:_GetNextCargoTransport() + + -- Abs. mission time in seconds. + local Time=timer.getAbsTime() + + -- Current position. + local coord=self:GetCoordinate() + + -- Sort results table wrt prio and distance to pickup zone. + local function _sort(a, b) + local transportA=a --#OPSGROUP.CargoTransport + local transportB=b --#OPSGROUP.CargoTransport + local distA=transportA.pickupzone:GetCoordinate():Get2DDistance(coord) + local distB=transportB.pickupzone:GetCoordinate():Get2DDistance(coord) + return (transportA.prio=cargotransport.Tstart and cargotransport.status==OPSGROUP.TransportStatus.SCHEDULED and (cargotransport.importance==nil or cargotransport.importance<=vip) then + return cargotransport + end + + end + + return nil +end + +--- Check if all cargo of this transport assignment was delivered. +-- @param #OPSGROUP self +-- @param #OPSGROUP.CargoTransport The next due cargo transport or `nil`. +-- @return #boolean If true, all cargo was delivered. +function OPSGROUP:_CheckDelivered(CargoTransport) + + -- TODO: Check if this group can actually transport any cargo. + + local done=true + for _,_cargo in pairs(CargoTransport.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + + 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. + else + done=false --Someone is not done! + end + + --TODO: check if ALL remaining cargo is too heavy for this carrier group ==> el + + end + + if done then + self:Delivered(CargoTransport) + end + + -- Debug info. + self:I(self.lid..string.format("Cargotransport UID=%d Status=%s: delivered=%s", CargoTransport.uid, CargoTransport.status, tostring(done))) + + return done +end + --- Create a cargo transport assignment. -- @param #OPSGROUP self -- @param Core.Set#SET_GROUP GroupSet Set of groups to be transported. Can also be a single @{Wrapper.Group#GROUP} object. @@ -4763,11 +4791,15 @@ function OPSGROUP:AddCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Impo -- Create a new cargo transport assignment. local cargotransport=self:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, NewCarrierGroup) - -- Set state to SCHEDULED. - cargotransport.status=OPSGROUP.TransportStatus.SCHEDULED + if cargotransport then - --Add to cargo queue - table.insert(self.cargoqueue, cargotransport) + -- Set state to SCHEDULED. + cargotransport.status=OPSGROUP.TransportStatus.SCHEDULED + + --Add to cargo queue + table.insert(self.cargoqueue, cargotransport) + + end return cargotransport end @@ -4816,7 +4848,7 @@ function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, I -- Data structure. local transport={} --#OPSGROUP.CargoTransport - transport.uid=1 + transport.uid=self.cargocounter transport.status=OPSGROUP.TransportStatus.PLANNING transport.pickupzone=Pickupzone transport.deployzone=Deployzone @@ -4830,15 +4862,27 @@ function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, I -- Check type of GroupSet provided. if GroupSet:IsInstanceOf("GROUP") or GroupSet:IsInstanceOf("OPSGROUP") then + -- We got a single GROUP or OPSGROUP objectg. local cargo=self:CreateCargoGroupData(GroupSet, Pickupzone, Deployzone) - table.insert(transport.cargos, cargo) - else - -- We got a SET_GROUP object. - for _,group in pairs(GroupSet.Set) do - local cargo=self:CreateCargoGroupData(group, Pickupzone, Deployzone) + + if cargo and self:CanCargo(cargo.opsgroup) then table.insert(transport.cargos, cargo) end + + else + + -- We got a SET_GROUP object. + + for _,group in pairs(GroupSet.Set) do + + local cargo=self:CreateCargoGroupData(group, Pickupzone, Deployzone) + + if cargo and self:CanCargo(cargo.opsgroup) then + table.insert(transport.cargos, cargo) + end + + end end -- Debug info. @@ -4856,7 +4900,13 @@ function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, I self:I(self.lid..text) end - return transport + if #transport.cargos>0 then + self.cargocounter=self.cargocounter+1 + return transport + else + self:E(self.lid.."ERROR: Cargo too heavy for this carrier group!") + return nil + end end --- Create a cargo group data structure. @@ -4884,7 +4934,7 @@ function OPSGROUP:CreateCargoGroupData(group, Pickupzone, Deployzone) opsgroup=ARMYGROUP:New(group) end else - env.info("FF found opsgroup in createcargo") + --env.info("FF found opsgroup in createcargo") end end @@ -4997,6 +5047,25 @@ function OPSGROUP:RedWeightCargo(UnitName, Weight) return self end +--- Check if the group can be carrier of a cargo group. +-- **Note** that the cargo group *cannot* be split into units, i.e. the largest cargo bay of any element of the group must be able to load the whole cargo group in one piece. +-- @param #OPSGROUP self +-- @param #OPSGROUP CargoGroup Cargo group, which needs a carrier. +-- @return #boolean If `true`, there is an element of the group that can load the whole cargo group. +function OPSGROUP:CanCargo(CargoGroup) + + local weight=CargoGroup:GetWeightTotal() + + for _,element in pairs(self.elements) do + local can=element.weightMaxCargo>=weight + if can then + return true + end + end + + return false +end + --- Add weight to the internal cargo of an element of the group. -- @param #OPSGROUP self -- @param #OPSGROUP CargoGroup Cargo group, which needs a carrier. @@ -5033,6 +5102,7 @@ function OPSGROUP:onafterPickup(From, Event, To, Zone) local airbasePickup=nil --Wrapper.Airbase#AIRBASE if Zone and Zone:IsInstanceOf("ZONE_AIRBASE") then + env.info("FF 001") airbasePickup=Zone:GetAirbase() end @@ -5041,16 +5111,21 @@ function OPSGROUP:onafterPickup(From, Event, To, Zone) if self:IsArmygroup() or self:IsNavygroup() then ready4loading=inzone else + env.info("FF 002 currbase="..(self.currbase and self.currbase:GetName() or "unknown")) + -- Aircraft is already parking at the pickup airbase. ready4loading=self.currbase and self.currbase:GetName()==Zone:GetName() and self:IsParking() -- If a helo is landed in the zone, we also are ready for loading. - if ready4loading==false and self.isHelo and self:IsLandedAt() and inzone then + if ready4loading==false and (self.isHelo or self.isVTOL) and self:IsLandedAt() and inzone then ready4loading=true + env.info("FF 003") end end if ready4loading then + + env.info("FF 004") -- We are already in the pickup zone ==> initiate loading. self:Loading() @@ -5062,24 +5137,28 @@ function OPSGROUP:onafterPickup(From, Event, To, Zone) -- Add waypoint. if self.isFlightgroup then - - --- - -- Pickup at airbase - --- if airbasePickup then + + --- + -- Pickup at airbase + --- local airbaseCurrent=self.currbase if airbaseCurrent then + + env.info("FF 100") -- Activate uncontrolled group. if self:IsParking() then + env.info("FF 200") self:StartUncontrolled() end else -- Order group to land at an airbase. + env.info("FF 300") self:LandAtAirbase(airbasePickup) end @@ -5286,7 +5365,7 @@ function OPSGROUP:onafterTransport(From, Event, To, Zone) ready2deploy=self.currbase and self.currbase:GetName()==Zone:GetName() and self:IsParking() -- If a helo is landed in the zone, we also are ready for loading. - if ready2deploy==false and self.isHelo and self:IsLandedAt() and inzone then + if ready2deploy==false and (self.isHelo or self.isVTOL) and self:IsLandedAt() and inzone then ready2deploy=true end end @@ -5454,6 +5533,7 @@ function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Heading) --- OpsGroup:Unboard(Coordinate, Heading) + else --- @@ -5480,7 +5560,6 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Core.Zone#ZONE Zone Deploy zone. function OPSGROUP:onafterUnloaded(From, Event, To) -- Debug info self:I(self.lid.."Cargo unloaded..") @@ -5519,8 +5598,8 @@ function OPSGROUP:onafterUnloaded(From, Event, To) -- This is not a carrier anymore. self.carrierStatus=OPSGROUP.CarrierStatus.NOTCARRIER - -- No current transport assignment. - self.cargoTransport=nil + -- Everything delivered. + self:Delivered(self.cargoTransport) -- Startup uncontrolled aircraft to allow it to go back. if self:IsFlightgroup() then @@ -5537,6 +5616,27 @@ function OPSGROUP:onafterUnloaded(From, Event, To) end +--- On after "Delivered" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #OPSGROUP.CargoTransport CargoTransport +function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) + + -- Set cargo status. + CargoTransport.status=OPSGROUP.TransportStatus.DELIVERED + + -- Check if this was the current transport. + if self.cargoTransport and self.cargoTransport.uid==CargoTransport.uid then + self.cargoTransport=nil + end + + -- Remove cargo transport from cargo queue. + self:DelCargoTransport(CargoTransport) + +end + --- -- Cargo Group Functions @@ -5569,7 +5669,7 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) local Coordinate=Carrier.unit:GetCoordinate() if self.isArmygroup then - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) waypoint.detour=true else local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) @@ -5642,6 +5742,9 @@ function OPSGROUP:onafterUnboard(From, Event, To, Coordinate, Heading, Delay) -- Template for the respawned group. local Template=UTILS.DeepCopy(self.template) --DCS#Template + + -- No late activation. + Template.lateActivation=false -- Loop over template units. for _,Unit in pairs(Template.units) do @@ -7869,11 +7972,25 @@ function OPSGROUP:_AddElementByName(unitname) -- Weight and cargo. element.weightEmpty=element.descriptors.massEmpty or 666 - element.weightMaxTotal=element.descriptors.massMax or element.weightEmpty+2*95 --If max mass is not given, we assume 10 soldiers. - element.weightMaxCargo=math.max(element.weightMaxTotal-element.weightEmpty, 0) element.weightCargo=0 - element.weight=element.weightEmpty+element.weightCargo + element.weight=element.weightEmpty+element.weightCargo + -- Looks like only aircraft have a massMax value in the descriptors. + element.weightMaxTotal=element.descriptors.massMax or element.weightEmpty+2*95 --If max mass is not given, we assume 10 soldiers. + + + if self.isArmygroup then + + element.weightMaxTotal=element.weightEmpty+10*95 --If max mass is not given, we assume 10 soldiers. + + elseif self.isNavygroup then + + element.weightMaxTotal=element.weightEmpty+10*1000 + + end + + -- Max cargo weight + element.weightMaxCargo=math.max(element.weightMaxTotal-element.weightEmpty, 0) -- FLIGHTGROUP specific. if self.isFlightgroup then