diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 8143106e1..4dd1d2bd5 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -156,7 +156,7 @@ function ARMYGROUP:New(group) --self:HandleEvent(EVENTS.Hit, self.OnEventHit) -- Start the status monitoring. - self:__Status(-1) + self.timerStatus=TIMER:New(self.Status, self):Start(1, 30) -- Start queue update timer. self.timerQueueUpdate=TIMER:New(self._QueueUpdate, self):Start(2, 5) @@ -343,30 +343,17 @@ end -- Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ----- Update status. --- @param #ARMYGROUP self -function ARMYGROUP:onbeforeStatus(From, Event, To) - - if self:IsDead() then - self:T(self.lid..string.format("Onbefore Status DEAD ==> false")) - return false - elseif self:IsStopped() then - self:T(self.lid..string.format("Onbefore Status STOPPED ==> false")) - return false - end - - return true -end - --- Update status. -- @param #ARMYGROUP self -function ARMYGROUP:onafterStatus(From, Event, To) +function ARMYGROUP:Status() -- FSM state. local fsmstate=self:GetState() local alive=self:IsAlive() + env.info(self.lid.."FF status="..fsmstate) + if alive then --- @@ -490,9 +477,6 @@ function ARMYGROUP:onafterStatus(From, Event, To) self:_PrintTaskAndMissionStatus() - - -- Next status update. - self:__Status(-30) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -596,9 +580,6 @@ function ARMYGROUP:onafterSpawned(From, Event, To) self:FullStop() end - -- Update status. - self:__Status(-0.1) - end end @@ -652,7 +633,7 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Formation) wp.speed=UTILS.KnotsToMps(Speed) else -- Take default waypoint speed. But make sure speed>0 if patrol ad infinitum. - if self.adinfinitum and wp.speed<0.1 then + if wp.speed<0.1 then --self.adinfinitum and wp.speed=UTILS.KmphToMps(self.speedCruise) end end @@ -1156,7 +1137,7 @@ function ARMYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation -- Check if final waypoint is still passed. if wpnumber>self.currentwp then - self.passedfinalwp=false + self:_PassedFinalWaypoint(false, "ARMYGROUP.AddWaypoint: wpnumber>self.currentwp") end -- Speed in knots. diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index d26c73f8e..bdc45fdad 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -90,6 +90,7 @@ -- @field Core.Set#SET_GROUP transportGroupSet Groups to be transported. -- @field Core.Point#COORDINATE transportPickup Coordinate where to pickup the cargo. -- @field Core.Point#COORDINATE transportDropoff Coordinate where to drop off the cargo. +-- @field Ops.OpsTransport#OPSTRANSPORT opstransport OPS transport assignment. -- -- @field #number artyRadius Radius in meters. -- @field #number artyShots Number of shots fired. @@ -328,6 +329,7 @@ _AUFTRAGSNR=0 -- @field #string TROOPTRANSPORT Troop transport mission. -- @field #string ARTY Fire at point. -- @field #string PATROLZONE Patrol a zone. +-- @field #string OPSTRANSPORT Ops transport. AUFTRAG.Type={ ANTISHIP="Anti Ship", AWACS="AWACS", @@ -352,6 +354,7 @@ AUFTRAG.Type={ TROOPTRANSPORT="Troop Transport", ARTY="Fire At Point", PATROLZONE="Patrol Zone", + OPSTRANSPORT="Ops Transport", } --- Mission status. @@ -1171,6 +1174,48 @@ function AUFTRAG:NewTROOPTRANSPORT(TransportGroupSet, DropoffCoordinate, PickupC return mission end + +--- Create a OPS TRANSPORT mission. +-- @param #AUFTRAG self +-- @param Core.Set#SET_GROUP CargoGroupSet The set group(s) to be transported. +-- @param Core.Zone#ZONE PickupZone Pick up zone +-- @param Core.Zone#ZONE DeployZone Deploy zone +-- @return #AUFTRAG self +function AUFTRAG:NewOPSTRANSPORT(CargoGroupSet, PickupZone, DeployZone) + + local mission=AUFTRAG:New(AUFTRAG.Type.OPSTRANSPORT) + + mission.transportGroupSet=CargoGroupSet + + mission:_TargetFromObject(mission.transportGroupSet) + + --mission.transportPickup=PickupCoordinate or mission:GetTargetCoordinate() + --mission.transportDropoff=DropoffCoordinate + + -- Debug. + --mission.transportPickup:MarkToAll("Pickup") + --mission.transportDropoff:MarkToAll("Drop off") + + mission.opstransport=OPSTRANSPORT:New(CargoGroupSet, PickupZone, DeployZone) + + function mission.opstransport:OnAfterExecuting(From, Event, To) + mission:Executing() + end + + function mission.opstransport:OnAfterDelivered(From, Event, To) + mission:Done() + end + + -- TODO: what's the best ROE here? + mission.optionROE=ENUMS.ROE.ReturnFire + mission.optionROT=ENUMS.ROT.PassiveDefense + + mission.DCStask=mission:GetDCSMissionTask() + + return mission +end + + --- Create an ARTY mission. -- @param #AUFTRAG self -- @param Core.Point#COORDINATE Target Center of the firing solution. @@ -1211,6 +1256,11 @@ function AUFTRAG:NewPATROLZONE(Zone, Speed, Altitude) local mission=AUFTRAG:New(AUFTRAG.Type.PATROLZONE) + -- Ensure we got a ZONE and not just the zone name. + if type(Zone)=="string" then + Zone=ZONE:New(Zone) + end + mission:_TargetFromObject(Zone) mission.optionROE=ENUMS.ROE.OpenFire @@ -1541,14 +1591,14 @@ end --- Get number of required assets. -- @param #AUFTRAG self --- @param Ops.Legion#Legion Legion (Optional) Only get the required assets for a specific legion. --- @param #number Number of required assets. +-- @param Ops.Legion#Legion Legion (Optional) Only get the required assets for a specific legion. If required assets for this legion are not defined, the total number is returned. +-- @return #number Number of required assets. function AUFTRAG:GetRequiredAssets(Legion) local N=self.nassets - if Legion then - N=self.Nassets[Legion.alias] or 0 + if Legion and self.Nassets[Legion.alias] then + N=self.Nassets[Legion.alias] end return N @@ -1669,6 +1719,43 @@ function AUFTRAG:SetMissionRange(Range) return self end +--- Attach OPS transport to the mission. Mission assets will be transported before the mission is started at the OPSGROUP level. +-- @param #AUFTRAG self +-- @param Ops.OpsTransport#OPSTRANSPORT OpsTransport The OPS transport assignment attached to the mission. +-- @return #AUFTRAG self +function AUFTRAG:SetOpsTransport(OpsTransport) + self.opstransport=OpsTransport + return self +end + +--- Attach OPS transport to the mission. Mission assets will be transported before the mission is started at the OPSGROUP level. +-- @param #AUFTRAG self +-- @param Core.Zone#ZONE PickupZone Zone where assets are picked up. +-- @param Core.Zone#ZONE DeployZone Zone where assets are deployed. +-- @param Core.Set#SET_OPSGROUP Carriers Set of carriers. Can also be a single group. Can also be added via the AddTransportCarriers functions. +-- @return #AUFTRAG self +function AUFTRAG:SetTransportForAssets(PickupZone, DeployZone, Carriers) + + -- OPS transport from pickup to deploy zone. + self.opstransport=OPSTRANSPORT:New(nil, PickupZone, DeployZone) + + if Carriers then + if Carriers:IsInstanceOf("SET_OPSGROUP") then + + for _,_carrier in pairs(Carriers.Set) do + local carrier=_carrier --Ops.OpsGroup#OPSGROUP + carrier:AddOpsTransport(self.opstransport) + end + + elseif Carriers:IsInstanceOf("OPSGROUP") then + Carriers:AddOpsTransport(self.opstransport) + end + + end + + return self +end + --- Set Rules of Engagement (ROE) for this mission. -- @param #AUFTRAG self -- @param #string roe Mission ROE. @@ -1959,6 +2046,11 @@ function AUFTRAG:AddOpsGroup(OpsGroup) groupdata.waypointtask=nil self.groupdata[OpsGroup.groupname]=groupdata + + -- Add ops transport to new group. + if self.opstransport then + self.opstransport:AddCargoGroups(OpsGroup) + end return self end @@ -2237,7 +2329,7 @@ function AUFTRAG:onafterStatus(From, Event, To) elseif (self.Tstop and Tnow>self.Tstop+10) or (Ntargets0>0 and Ntargets==0) then -- Cancel mission if stop time passed. - self:Cancel() + --self:Cancel() end @@ -3763,6 +3855,23 @@ function AUFTRAG:GetDCSMissionTask(TaskControllable) table.insert(DCStasks, TaskEmbark) table.insert(DCStasks, TaskDisEmbark) + elseif self.type==AUFTRAG.Type.OPSTRANSPORT then + + -------------------------- + -- OPSTRANSPORT Mission -- + -------------------------- + + local DCStask={} + + DCStask.id="OpsTransport" + + -- We create a "fake" DCS task and pass the parameters to the FLIGHTGROUP. + local param={} + DCStask.params=param + + table.insert(DCStasks, DCStask) + + elseif self.type==AUFTRAG.Type.RESCUEHELO then ------------------------- diff --git a/Moose Development/Moose/Ops/Brigade.lua b/Moose Development/Moose/Ops/Brigade.lua index d12483cf6..70d5505cd 100644 --- a/Moose Development/Moose/Ops/Brigade.lua +++ b/Moose Development/Moose/Ops/Brigade.lua @@ -243,6 +243,21 @@ function BRIGADE:onafterStatus(From, Event, To) self:I(self.lid..text) end + ---------------- + -- Transport --- + ---------------- + + -- Check if any transports should be cancelled. + --self:_CheckTransports() + + -- Get next mission. + local transport=self:_GetNextTransport() + + -- Request mission execution. + if transport then + self:TransportRequest(transport) + end + -------------- -- Mission --- -------------- diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index 596c86b78..37d6ad914 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -842,10 +842,21 @@ function COHORT:RecruitAssets(Mission, Npayloads) end -- Check if in a state where we really do not want to fight any more. - if flightgroup:IsHolding() or flightgroup:IsLanding() or flightgroup:IsLanded() or flightgroup:IsArrived() or flightgroup:IsDead() or flightgroup:IsStopped() then + if flightgroup:IsFlightgroup() then + if flightgroup:IsHolding() or flightgroup:IsLanding() or flightgroup:IsLanded() or flightgroup:IsArrived() then + combatready=false + end + else + if flightgroup:IsRearming() or flightgroup:IsRetreating() or flightgroup:IsReturning() then + combatready=false + end + end + -- Applies to all opsgroups. + if flightgroup:IsDead() or flightgroup:IsStopped() then combatready=false end + --TODO: Check transport for combat readyness! -- This asset is "combatready". diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index 240c5f522..9c5781277 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -1,15 +1,15 @@ ---- **Ops** - Commander of an Airwing, Brigade or Flotilla. +--- **Ops** - Commander of Airwings, Brigades and Flotillas. -- -- **Main Features:** -- -- * Manages AIRWINGS, BRIGADEs and FLOTILLAs --- * Handles missions (AUFTRAG) and finds the best airwing for the job +-- * Handles missions (AUFTRAG) and finds the best man for the job -- -- === -- -- ### Author: **funkyfranky** --- @module Ops.WingCommander --- @image OPS_WingCommander.png +-- @module Ops.Commander +-- @image OPS_Commander.png --- COMMANDER class. @@ -28,7 +28,7 @@ -- -- # The COMMANDER Concept -- --- A wing commander is the head of legions. He will find the best AIRWING to perform an assigned AUFTRAG (mission). +-- A commander is the head of legions. He will find the best LEGIONs to perform an assigned AUFTRAG (mission). -- -- -- @field #COMMANDER @@ -36,7 +36,7 @@ COMMANDER = { ClassName = "COMMANDER", Debug = nil, lid = nil, - legions = {}, + legions = {}, missionqueue = {}, } @@ -48,7 +48,8 @@ COMMANDER.version="0.1.0" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Improve airwing selection. Mostly done! +-- TODO: Improve legion selection. Mostly done! +-- TODO: Allow multiple Legions for one mission. -- NOGO: Maybe it's possible to preselect the assets for the mission. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -145,16 +146,29 @@ end -- User functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Add an airwing to the wingcommander. +--- Add an AIRWING to the commander. -- @param #COMMANDER self -- @param Ops.AirWing#AIRWING Airwing The airwing to add. -- @return #COMMANDER self function COMMANDER:AddAirwing(Airwing) - -- This airwing is managed by this wing commander. - Airwing.commander=self + -- Add legion. + self:AddLegion(Airwing) + + return self +end - table.insert(self.legions, Airwing) +--- Add a LEGION to the commander. +-- @param #COMMANDER self +-- @param Ops.Legion#LEGION Legion The legion to add. +-- @return #COMMANDER self +function COMMANDER:AddLegion(Legion) + + -- This legion is managed by the commander. + Legion.commander=self + + -- Add to legions. + table.insert(self.legions, Legion) return self end @@ -237,21 +251,21 @@ function COMMANDER:onafterStatus(From, Event, To) self:CheckMissionQueue() -- Status. - local text=string.format("Status %s: Airwings=%d, Missions=%d", fsmstate, #self.legions, #self.missionqueue) + local text=string.format("Status %s: Legions=%d, Missions=%d", fsmstate, #self.legions, #self.missionqueue) self:I(self.lid..text) - -- Airwing Info + -- Legion info. if #self.legions>0 then - local text="Airwings:" - for _,_airwing in pairs(self.legions) do - local airwing=_airwing --Ops.AirWing#AIRWING - local Nassets=airwing:CountAssets() - local Nastock=airwing:CountAssets(true) - text=text..string.format("\n* %s [%s]: Assets=%s stock=%s", airwing.alias, airwing:GetState(), Nassets, Nastock) + local text="Legions:" + for _,_legion in pairs(self.legions) do + local legion=_legion --Ops.Legion#LEGION + local Nassets=legion:CountAssets() + local Nastock=legion:CountAssets(true) + text=text..string.format("\n* %s [%s]: Assets=%s stock=%s", legion.alias, legion:GetState(), Nassets, Nastock) for _,aname in pairs(AUFTRAG.Type) do - local na=airwing:CountAssets(true, {aname}) - local np=airwing:CountPayloadsInStock({aname}) - local nm=airwing:CountAssetsOnMission({aname}) + local na=legion:CountAssets(true, {aname}) + local np=legion:CountPayloadsInStock({aname}) + local nm=legion:CountAssetsOnMission({aname}) if na>0 or np>0 then text=text..string.format("\n - %s: assets=%d, payloads=%d, on mission=%d", aname, na, np, nm) end @@ -353,23 +367,26 @@ function COMMANDER:CheckMissionQueue() local mission=_mission --Ops.Auftrag#AUFTRAG -- We look for PLANNED missions. - if mission.status==AUFTRAG.Status.PLANNED then + if mission:IsPlanned() then --- -- PLANNNED Mission --- - local airwings=self:GetLegionsForMission(mission) + -- Get legions for mission. + local legions=self:GetLegionsForMission(mission) - if airwings then + if legions then - for _,airwing in pairs(airwings) do + for _,_legion in pairs(legions) do + local legion=_legion --Ops.Legion#LEGION - -- Add mission to airwing. - self:MissionAssign(airwing, mission) + -- Add mission to legion. + self:MissionAssign(legion, mission) end + -- Only ONE mission is assigned. return end @@ -395,31 +412,36 @@ function COMMANDER:GetLegionsForMission(Mission) local legions={} -- Loop over all legions. - for _,_airwing in pairs(self.legions) do - local airwing=_airwing --Ops.AirWing#AIRWING + for _,_legion in pairs(self.legions) do + local legion=_legion --Ops.Legion#LEGION - -- Check if airwing can do this mission. - local can,assets=airwing:CanMission(Mission) + -- Count number of assets in stock. + local Nassets=0 + if legion:IsAirwing() then + Nassets=legion:CountAssetsWithPayloadsInStock(Mission.payloads, {Mission.type}, Attributes) + else + Nassets=legion:CountAssets(true, {Mission.type}, Attributes) --Could also specify the attribute if Air or Ground mission. + end -- Has it assets that can? - if #assets>0 then + if Nassets>0 then -- Get coordinate of the target. local coord=Mission:GetTargetCoordinate() if coord then - -- Distance from airwing to target. - local distance=UTILS.MetersToNM(coord:Get2DDistance(airwing:GetCoordinate())) + -- Distance from legion to target. + local distance=UTILS.MetersToNM(coord:Get2DDistance(legion:GetCoordinate())) -- Round: 55 NM ==> 5.5 ==> 6, 63 NM ==> 6.3 ==> 6 local dist=UTILS.Round(distance/10, 0) -- Debug info. - self:I(self.lid..string.format("Got legion %s with Nassets=%d and dist=%.1f NM, rounded=%.1f", airwing.alias, #assets, distance, dist)) + self:I(self.lid..string.format("Got legion %s with Nassets=%d and dist=%.1f NM, rounded=%.1f", legion.alias, Nassets, distance, dist)) - -- Add airwing to table of legions that can. - table.insert(legions, {airwing=airwing, distance=distance, dist=dist, targetcoord=coord, nassets=#assets}) + -- Add legion to table of legions that can. + table.insert(legions, {airwing=legion, distance=distance, dist=dist, targetcoord=coord, nassets=Nassets}) end @@ -431,8 +453,8 @@ function COMMANDER:GetLegionsForMission(Mission) if #legions>0 then --- Something like: - -- * Closest airwing that can should be first prio. - -- * However, there should be a certain "quantization". if wing is 50 or 60 NM way should not really matter. In that case, the airwing with more resources should get the job. + -- * Closest legion that can should be first prio. + -- * However, there should be a certain "quantization". if wing is 50 or 60 NM way should not really matter. In that case, the legion with more resources should get the job. local function score(a) local d=math.round(a.dist/10) end @@ -440,7 +462,7 @@ function COMMANDER:GetLegionsForMission(Mission) env.info(self.lid.."FF #legions="..#legions) -- Sort table wrt distance and number of assets. - -- Distances within 10 NM are equal and the airwing with more assets is preferred. + -- Distances within 10 NM are equal and the legion with more assets is preferred. local function sortdist(a,b) local ad=a.dist local bd=b.dist @@ -471,7 +493,7 @@ function COMMANDER:GetLegionsForMission(Mission) self:I(self.lid..string.format("Found %d legions that can do mission %s (%s) requiring %d assets", #selection, Mission:GetName(), Mission:GetType(), Mission.nassets)) return selection else - self:T(self.lid..string.format("Not enough LEGIONs found that could do the job :/")) + self:T(self.lid..string.format("Not enough LEGIONs found that could do the job :/ Number of assets avail %d < %d required for the mission", N, Mission.nassets)) return nil end @@ -482,17 +504,18 @@ function COMMANDER:GetLegionsForMission(Mission) return nil end ---- Check mission queue and assign ONE planned mission. +--- Count assets of all assigned legions. -- @param #COMMANDER self -- @param #boolean InStock If true, only assets that are in the warehouse stock/inventory are counted. -- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types. -- @param #table Attributes (Optional) Count only assest that have a certain attribute(s), e.g. `WAREHOUSE.Attribute.AIR_BOMBER`. --- @return #number Amount of asset groups in stock. +-- @return #number Amount of asset groups. function COMMANDER:CountAssets(InStock, MissionTypes, Attributes) + local N=0 - for _,_airwing in pairs(self.legions) do - local airwing=_airwing --Ops.AirWing#AIRWING - N=N+airwing:CountAssets(InStock, MissionTypes, Attributes) + for _,_legion in pairs(self.legions) do + local legion=_legion --Ops.Legion#LEGION + N=N+legion:CountAssets(InStock, MissionTypes, Attributes) end return N diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 2787b615a..c7483e648 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -230,13 +230,13 @@ function FLIGHTGROUP:New(group) -- Add FSM transitions. -- From State --> Event --> To State - self:AddTransition("*", "LandAtAirbase", "Inbound") -- Helo group is ordered to land at a specific point. - self:AddTransition("*", "RTB", "Inbound") -- Group is returning to destination base. + self:AddTransition("*", "LandAtAirbase", "Inbound") -- Group is ordered to land at an airbase. + self:AddTransition("*", "RTB", "Inbound") -- Group is returning to (home/destination) airbase. self:AddTransition("*", "RTZ", "Inbound") -- Group is returning to destination zone. Not implemented yet! self:AddTransition("Inbound", "Holding", "Holding") -- Group is in holding pattern. self:AddTransition("*", "Refuel", "Going4Fuel") -- Group is send to refuel at a tanker. - self:AddTransition("Going4Fuel", "Refueled", "Airborne") -- Group finished refueling. + self:AddTransition("Going4Fuel", "Refueled", "Cruising") -- Group finished refueling. self:AddTransition("*", "LandAt", "LandingAt") -- Helo group is ordered to land at a specific point. self:AddTransition("LandingAt", "LandedAt", "LandedAt") -- Helo group landed landed at a specific point. @@ -244,12 +244,8 @@ function FLIGHTGROUP:New(group) self:AddTransition("*", "FuelLow", "*") -- Fuel state of group is low. Default ~25%. self:AddTransition("*", "FuelCritical", "*") -- Fuel state of group is critical. Default ~10%. - self:AddTransition("*", "OutOfMissilesAA", "*") -- Group is out of A2A (air) missiles. - self:AddTransition("*", "OutOfMissilesAG", "*") -- Group is out of A2G (ground) missiles. - self:AddTransition("*", "OutOfMissilesAS", "*") -- Group is out of A2S (ship) missiles. - - self:AddTransition("Airborne", "EngageTarget", "Engaging") -- Engage targets. - self:AddTransition("Engaging", "Disengage", "Airborne") -- Engagement over. + self:AddTransition("Cruising", "EngageTarget", "Engaging") -- Engage targets. + self:AddTransition("Engaging", "Disengage", "Cruising") -- Engagement over. self:AddTransition("*", "ElementParking", "*") -- An element is parking. self:AddTransition("*", "ElementEngineOn", "*") -- An element spooled up the engines. @@ -305,7 +301,7 @@ function FLIGHTGROUP:New(group) self:_InitGroup() -- Start the status monitoring. - self:__Status(-1) + self.timerStatus=TIMER:New(self.Status, self):Start(1, 30) -- Start queue update timer. self.timerQueueUpdate=TIMER:New(self._QueueUpdate, self):Start(2, 5) @@ -662,6 +658,15 @@ function FLIGHTGROUP:IsFuelCritical() return self.fuelcritical end +--- Check if flight is good on fuel (not below low or even critical state). +-- @param #FLIGHTGROUP self +-- @return #boolean If true, flight is good on fuel. +function FLIGHTGROUP:IsFuelGood() + local isgood=not (self.fuellow or self.fuelcritical) + return isgood +end + + --- Check if flight can do air-to-ground tasks. -- @param #FLIGHTGROUP self -- @param #boolean ExcludeGuns If true, exclude gun @@ -830,15 +835,14 @@ function FLIGHTGROUP:onbeforeStatus(From, Event, To) return true end ---- On after "Status" event. +--- Status update. -- @param #FLIGHTGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function FLIGHTGROUP:onafterStatus(From, Event, To) +function FLIGHTGROUP:Status() -- FSM state. local fsmstate=self:GetState() + + env.info(self.lid.."FF status="..fsmstate) -- Update position. self:_UpdatePosition() @@ -894,8 +898,8 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) local fc=self.flightcontrol and self.flightcontrol.airbasename or "N/A" local curr=self.currbase and self.currbase:GetName() or "N/A" - local text=string.format("Status %s [%d/%d]: Tasks=%d, Missions=%s, Waypoint=%d/%d, Detected=%d, Home=%s, Destination=%s, Current=%s, FC=%s", - fsmstate, #self.elements, #self.elements, nTaskTot, nMissions, self.currentwp or 0, self.waypoints and #self.waypoints or 0, + local text=string.format("Status %s [%d/%d]: Tasks=%d, Missions=%s, Waypoint=%d/%d [%s], Detected=%d, Home=%s, Destination=%s, Current=%s, FC=%s", + fsmstate, #self.elements, #self.elements, nTaskTot, nMissions, self.currentwp or 0, self.waypoints and #self.waypoints or 0, tostring(self.passedfinalwp), self.detectedunits:Count(), home, dest, curr, fc) self:I(self.lid..text) @@ -1024,33 +1028,6 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) self:FuelCritical() end - -- This causes severe problems as OutOfMissiles is called over and over again leading to many RTB calls. - if false then - - -- Out of AA Missiles? CAP, GCICAP, INTERCEPT - local CurrIsCap = false - -- Out of AG Missiles? BAI, SEAD, CAS, STRIKE - local CurrIsA2G = false - -- Check AUFTRAG Type - local CurrAuftrag = self:GetMissionCurrent() - if CurrAuftrag then - local CurrAuftragType = CurrAuftrag:GetType() - if CurrAuftragType == "CAP" or CurrAuftragType == "GCICAP" or CurrAuftragType == "INTERCEPT" then CurrIsCap = true end - if CurrAuftragType == "BAI" or CurrAuftragType == "CAS" or CurrAuftragType == "SEAD" or CurrAuftragType == "STRIKE" then CurrIsA2G = true end - end - - -- Check A2A - if (not self:CanAirToAir(true)) and CurrIsCap then - self:OutOfMissilesAA() - end - - -- Check A2G - if (not self:CanAirToGround(false)) and CurrIsA2G then - self:OutOfMissilesAG() - end - - end - end --- @@ -1065,7 +1042,7 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) --- -- Engage Detected Targets --- - if self:IsAirborne() and self.detectionOn and self.engagedetectedOn and not (self.fuellow or self.fuelcritical) then + if self:IsAirborne() and self:IsFuelGood() and self.detectionOn and self.engagedetectedOn then -- Target. local targetgroup=nil --Wrapper.Group#GROUP @@ -1153,11 +1130,6 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) self:_CheckCargoTransport() - - -- Next check in ~30 seconds. - if not self:IsStopped() then - self:__Status(-30) - end end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1658,9 +1630,6 @@ function FLIGHTGROUP:onafterSpawned(From, Event, To) self:GetGroup():SetOption(AI.Option.Air.id.RTB_ON_BINGO, false) --self.group:SetOption(AI.Option.Air.id.RADAR_USING, AI.Option.Air.val.RADAR_USING.FOR_CONTINUOUS_SEARCH) - -- Update status. - self:__Status(-0.1) - -- Update route. self:__UpdateRoute(-0.5) @@ -2338,7 +2307,7 @@ function FLIGHTGROUP:onbeforeRTB(From, Event, To, airbase, SpeedTo, SpeedHold) end -- Only if fuel is not low or critical. - if not (self:IsFuelLow() or self:IsFuelCritical()) then + if self:IsFuelGood() then -- Check if there are remaining tasks. local Ntot,Nsched, Nwp=self:CountRemainingTasks() @@ -2442,7 +2411,7 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) self.currbase=airbase -- Passed final waypoint! - self.passedfinalwp=true + self:_PassedFinalWaypoint(true, "_LandAtAirbase") -- Not waiting any more. self.Twaiting=nil @@ -2477,7 +2446,7 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) p1=HoldingPoint.pos1 -- Debug marks. - if self.Debug then + if false then p0:MarkToAll("Holding point P0") p1:MarkToAll("Holding point P1") end @@ -3453,7 +3422,7 @@ function FLIGHTGROUP:InitWaypoints() -- Check if only 1 wp? if #self.waypoints==1 then - self.passedfinalwp=true + self:_PassedFinalWaypoint(true, "FLIGHTGROUP:InitWaypoints #self.waypoints==1") end end @@ -3475,7 +3444,7 @@ function FLIGHTGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Altitud local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID) if wpnumber>self.currentwp then - self.passedfinalwp=false + self:_PassedFinalWaypoint(false, "FLIGHTGROUP:AddWaypoint wpnumber>self.currentwp") end -- Speed in knots. @@ -3520,7 +3489,7 @@ function FLIGHTGROUP:AddWaypointLanding(Airbase, Speed, AfterWaypointWithID, Alt local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID) if wpnumber>self.currentwp then - self.passedfinalwp=false + self:_PassedFinalWaypoint(false, "AddWaypointLanding") end -- Speed in knots. @@ -3929,11 +3898,6 @@ function FLIGHTGROUP:GetParking(airbase) -- Debug output for occupied spots. self:T2(self.lid..string.format("Parking spot %d is occupied or not big enough!", parkingspot.TerminalID)) - --if self.Debug then - -- local coord=problem.coord --Core.Point#COORDINATE - -- local text=string.format("Obstacle blocking spot #%d is %s type %s with size=%.1f m and distance=%.1f m.", _termid, problem.name, problem.type, problem.size, problem.dist) - -- coord:MarkToAll(string.format(text)) - --end end diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 8c83911b0..06e63f37b 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -15,7 +15,9 @@ -- @field #number verbose Verbosity of output. -- @field #string lid Class id string for output to DCS log file. -- @field #table missionqueue Mission queue table. +-- @field #table transportqueue Transport queue. -- @field #table cohorts Cohorts of this legion. +-- @field Ops.Commander#COMMANDER commander Commander of this legion. -- @extends Functional.Warehouse#WAREHOUSE --- Be surprised! @@ -34,6 +36,7 @@ LEGION = { verbose = 0, lid = nil, missionqueue = {}, + transportqueue = {}, cohorts = {}, } @@ -76,6 +79,8 @@ function LEGION:New(WarehouseName, LegionName) self:AddTransition("*", "MissionRequest", "*") -- Add a (mission) request to the warehouse. self:AddTransition("*", "MissionCancel", "*") -- Cancel mission. + self:AddTransition("*", "TransportRequest", "*") -- Add a (mission) request to the warehouse. + self:AddTransition("*", "OpsOnMission", "*") -- An OPSGROUP was send on a Mission (AUFTRAG). self:AddTransition("*", "FlightOnMission", "*") -- An OPSGROUP was send on a Mission (AUFTRAG). self:AddTransition("*", "ArmyOnMission", "*") -- An OPSGROUP was send on a Mission (AUFTRAG). @@ -138,9 +143,9 @@ function LEGION:SetVerbosity(VerbosityLevel) return self end ---- Add a mission for the airwing. The airwing will pick the best available assets for the mission and lauch it when ready. +--- Add a mission for the legion. It will pick the best available assets for the mission and lauch it when ready. -- @param #LEGION self --- @param Ops.Auftrag#AUFTRAG Mission Mission for this airwing. +-- @param Ops.Auftrag#AUFTRAG Mission Mission for this legion. -- @return #LEGION self function LEGION:AddMission(Mission) @@ -184,6 +189,27 @@ function LEGION:RemoveMission(Mission) return self end +--- Add transport assignment to queue. +-- @param #LEGION self +-- @param Ops.OpsTransport#OPSTRANSPORT OpsTransport Transport assignment. +-- @return #LEGION self +function LEGION:AddOpsTransport(OpsTransport) + + -- Is not queued at a legion. + OpsTransport:Queued() + + -- Add mission to queue. + table.insert(self.transportqueue, OpsTransport) + + -- Info text. + local text=string.format("Added Transport %s. Starting at %s-%s", + tostring(OpsTransport.uid), UTILS.SecondsToClock(OpsTransport.Tstart, true), OpsTransport.Tstop and UTILS.SecondsToClock(OpsTransport.Tstop, true) or "INF") + self:T(self.lid..text) + + return self +end + + --- Get cohort by name. -- @param #LEGION self -- @param #string CohortName Name of the platoon. @@ -261,6 +287,7 @@ function LEGION:_CheckMissions() end end + --- Get next mission. -- @param #LEGION self -- @return Ops.Auftrag#AUFTRAG Next mission or `#nil`. @@ -301,7 +328,7 @@ function LEGION:_GetNextMission() -- Firstly, check if mission is due? if mission:IsQueued(self) and mission:IsReadyToGo() and (mission.importance==nil or mission.importance<=vip) then - -- Check if airwing can do the mission and gather required assets. + -- Check if legion can do the mission and gather required assets. local can, assets=self:CanMission(mission) -- Check that mission is still scheduled, time has passed and enough assets are available. @@ -391,6 +418,72 @@ function LEGION:_GetNextMission() return nil end +--- Get next transport. +-- @param #LEGION self +-- @return Ops.OpsTransport#OPSTRANSPORT Next transport or `#nil`. +function LEGION:_GetNextTransport() + + -- Number of missions. + local Ntransports=#self.transportqueue + + -- Treat special cases. + if Ntransports==0 then + return nil + end + + + local function getAssets(n) + local assets={} + + -- Loop over assets. + for _,_cohort in pairs(self.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + + if cohort:CheckMissionCapability({AUFTRAG.Type.OPSTRANSPORT}, cohort.missiontypes) then + + for _,_asset in pairs(cohort.assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + -- Check if asset is currently on a mission (STARTED or QUEUED). + if not asset.spawned then + + -- Add to assets. + table.insert(assets, asset) + + if #assets==n then + return assets + end + end + + end + + end + end + end + + + -- Look for first task that is not accomplished. + for _,_transport in pairs(self.transportqueue) do + local transport=_transport --Ops.OpsTransport#OPSTRANSPORT + + -- Check if transport is still queued and ready. + if transport:IsQueued() and transport:IsReadyToGo() then + + local assets=getAssets(1) + + if #assets>0 then + transport.assets=assets + return transport + end + + end + + end + + + return nil +end + --- Calculate the mission score of an asset. -- @param #LEGION self -- @param Functional.Warehouse#WAREHOUSE.Assetitem asset Asset @@ -518,7 +611,7 @@ function LEGION:onafterMissionRequest(From, Event, To, Mission) Mission:Requested() -- Set legion status. Ensures that it is not considered in the next selection. - Mission:SetLegionStatus(self, AUFTRAG.Status.REQUESTED) + Mission:SetLegionStatus(self, AUFTRAG.Status.REQUESTED) --- -- Some assets might already be spawned and even on a different mission (orbit). @@ -556,7 +649,7 @@ function LEGION:onafterMissionRequest(From, Event, To, Mission) end end - -- Add request to airwing warehouse. + -- Add request to legion warehouse. if #Assetlist>0 then --local text=string.format("Requesting assets for mission %s:", Mission.name) @@ -572,9 +665,11 @@ function LEGION:onafterMissionRequest(From, Event, To, Mission) end - -- Add request to airwing warehouse. - -- TODO: better Assignment string. - self:AddRequest(self, WAREHOUSE.Descriptor.ASSETLIST, Assetlist, #Assetlist, nil, nil, Mission.prio, tostring(Mission.auftragsnummer)) + -- TODO: Get/set functions for assignment string. + local assignment=string.format("Mission-%d", Mission.auftragsnummer) + + -- Add request to legion warehouse. + self:AddRequest(self, WAREHOUSE.Descriptor.ASSETLIST, Assetlist, #Assetlist, nil, nil, Mission.prio, assignment) -- The queueid has been increased in the onafterAddRequest function. So we can simply use it here. Mission.requestID[self.alias]=self.queueid @@ -582,6 +677,50 @@ function LEGION:onafterMissionRequest(From, Event, To, Mission) end +--- On after "MissionRequest" event. Performs a self request to the warehouse for the mission assets. Sets mission status to REQUESTED. +-- @param #LEGION self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.OpsTransport#OPSTRANSPORT Opstransport The requested mission. +function LEGION:onafterTransportRequest(From, Event, To, OpsTransport) + + -- Set mission status from QUEUED to REQUESTED. + OpsTransport:Requested() + + -- Set legion status. Ensures that it is not considered in the next selection. + --Mission:SetLegionStatus(self, AUFTRAG.Status.REQUESTED) + + -- Add request to legion warehouse. + if #OpsTransport.assets>0 then + + --local text=string.format("Requesting assets for mission %s:", Mission.name) + for i,_asset in pairs(OpsTransport.assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + -- Set asset to requested! Important so that new requests do not use this asset! + asset.requested=true + + -- Check max required transports. + if i==1 then + break + end + + end + + -- TODO: Get/set functions for assignment string. + local assignment=string.format("Transport-%d", OpsTransport.uid) + + -- Add request to legion warehouse. + self:AddRequest(self, WAREHOUSE.Descriptor.ASSETLIST, OpsTransport.assets, #OpsTransport.assets, nil, nil, OpsTransport.prio, assignment) + + -- The queueid has been increased in the onafterAddRequest function. So we can simply use it here. + OpsTransport.requestID=OpsTransport.requestID or {} + OpsTransport.requestID[self.alias]=self.queueid + end + +end + --- On after "MissionCancel" event. Cancels the missions of all flightgroups. Deletes request from warehouse queue. -- @param #LEGION self -- @param #string From From state. @@ -616,35 +755,6 @@ function LEGION:onafterMissionCancel(From, Event, To, Mission) end end - - --[[ - if Mission:IsPlanned() or Mission:IsQueued() or Mission:IsRequested() or Ngroups == 0 then - - Mission:Done() - - else - - for _,_asset in pairs(Mission.assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - - -- Asset should belong to this legion. - if asset.wid==self.uid then - - local opsgroup=asset.flightgroup - - if opsgroup then - opsgroup:MissionCancel(Mission) - end - - -- Not requested any more (if it was). - asset.requested=nil - - end - end - - end - ]] - -- Remove queued request (if any). if Mission.requestID[self.alias] then self:_DeleteQueueItemByID(Mission.requestID[self.alias], self.queue) @@ -683,7 +793,7 @@ end -- @param #string assignment The (optional) assignment for the asset. function LEGION:onafterNewAsset(From, Event, To, asset, assignment) - -- Call parent warehouse function first. + -- Call parent WAREHOUSE function first. self:GetParent(self, LEGION).onafterNewAsset(self, From, Event, To, asset, assignment) -- Debug text. @@ -697,6 +807,10 @@ function LEGION:onafterNewAsset(From, Event, To, asset, assignment) if cohort then if asset.assignment==assignment then + + --- + -- Asset is added to the COHORT for the first time + --- local nunits=#asset.template.units @@ -750,7 +864,11 @@ function LEGION:onafterNewAsset(From, Event, To, asset, assignment) --asset.terminalType=AIRBASE.TerminalType.OpenBig else - --env.info("FF cohort asset returned") + --- + -- Asset is returned to the COHORT + --- + + -- Trigger event. self:AssetReturned(cohort, asset) end @@ -758,7 +876,7 @@ function LEGION:onafterNewAsset(From, Event, To, asset, assignment) end end ---- On after "AssetReturned" event. Triggered when an asset group returned to its airwing. +--- On after "AssetReturned" event. Triggered when an asset group returned to its legion. -- @param #LEGION self -- @param #string From From state. -- @param #string Event Event. @@ -859,41 +977,66 @@ function LEGION:onafterAssetSpawned(From, Event, To, group, asset, request) flightgroup:SetFuelLowRefuel(cohort.fuellowRefuel) end - --- - -- Mission - --- - - -- Get Mission (if any). - local mission=self:GetMissionByID(request.assignment) - - -- Add mission to flightgroup queue. - if mission then - - if Tacan then - --mission:SetTACAN(Tacan, Morse, UnitName, Band) - end + -- Assignment. + local assignment=request.assignment + + if string.find(assignment, "Mission-") then + --- + -- Mission + --- + + local uid=UTILS.Split(assignment, "-")[2] + + -- Get Mission (if any). + local mission=self:GetMissionByID(uid) + -- Add mission to flightgroup queue. - asset.flightgroup:AddMission(mission) - - -- Trigger event. - self:__OpsOnMission(5, flightgroup, mission) - - else - - if Tacan then - --flightgroup:SwitchTACAN(Tacan, Morse, UnitName, Band) + if mission then + + if Tacan then + --mission:SetTACAN(Tacan, Morse, UnitName, Band) + end + + -- Add mission to flightgroup queue. + flightgroup:AddMission(mission) + + -- Trigger event. + self:__OpsOnMission(5, flightgroup, mission) + + else + + if Tacan then + --flightgroup:SwitchTACAN(Tacan, Morse, UnitName, Band) + end + end + + -- Add group to the detection set of the CHIEF (INTEL). + if self.commander and self.commander.chief then + self.commander.chief.detectionset:AddGroup(asset.flightgroup.group) + end + + elseif string.find(assignment, "Transport-") then + + --- + -- Transport + --- + + local uid=UTILS.Split(assignment, "-")[2] + -- Get Mission (if any). + local transport=self:GetTransportByID(uid) + + -- Add mission to flightgroup queue. + if transport then + flightgroup:AddOpsTransport(transport) + end + end - - -- Add group to the detection set of the WINGCOMMANDER. - if self.wingcommander and self.wingcommander.chief then - self.wingcommander.chief.detectionset:AddGroup(asset.flightgroup.group) - end - + end - + end --- On after "AssetDead" event triggered when an asset group died. @@ -907,11 +1050,11 @@ function LEGION:onafterAssetDead(From, Event, To, asset, request) -- Call parent warehouse function first. self:GetParent(self, LEGION).onafterAssetDead(self, From, Event, To, asset, request) - - -- Add group to the detection set of the WINGCOMMANDER. - if self.wingcommander and self.wingcommander.chief then - self.wingcommander.chief.detectionset:RemoveGroupsByName({asset.spawngroupname}) - end + + -- Remove group from the detection set of the CHIEF (INTEL). + if self.commander and self.commander.chief then + self.commander.chief.detectionset:RemoveGroupsByName({asset.spawngroupname}) + end -- Remove asset from mission is done via Mission:AssetDead() call from flightgroup onafterFlightDead function -- Remove asset from squadron same @@ -1203,7 +1346,7 @@ function LEGION:CountMissionsInQueue(MissionTypes) return N end ---- Count total number of assets that are in the warehouse stock (not spawned). +--- Count total number of assets of the legion. -- @param #LEGION self -- @param #boolean InStock If true, only assets that are in the warehouse stock/inventory are counted. -- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types. @@ -1221,6 +1364,54 @@ function LEGION:CountAssets(InStock, MissionTypes, Attributes) return N end +--- Count total number of assets in LEGION warehouse stock that also have a payload. +-- @param #LEGION self +-- @param #boolean Payloads (Optional) Specifc payloads to consider. Default all. +-- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types. +-- @param #table Attributes (Optional) Count only assest that have a certain attribute(s), e.g. `WAREHOUSE.Attribute.AIR_BOMBER`. +-- @return #number Amount of asset groups in stock. +function LEGION:CountAssetsWithPayloadsInStock(Payloads, MissionTypes, Attributes) + + -- Total number counted. + local N=0 + + -- Number of payloads in stock per aircraft type. + local Npayloads={} + + -- First get payloads for aircraft types of squadrons. + for _,_cohort in pairs(self.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + if Npayloads[cohort.aircrafttype]==nil then + Npayloads[cohort.aircrafttype]=self:CountPayloadsInStock(MissionTypes, cohort.aircrafttype, Payloads) + env.info(string.format("FF got Npayloads=%d for type=%s",Npayloads[cohort.aircrafttype], cohort.aircrafttype)) + end + end + + for _,_cohort in pairs(self.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + + -- Number of assets in stock. + local n=cohort:CountAssets(true, MissionTypes, Attributes) + + -- Number of payloads. + local p=Npayloads[cohort.aircrafttype] or 0 + + -- Only the smaller number of assets or paylods is really available. + local m=math.min(n, p) + + env.info("FF n="..n) + env.info("FF p="..p) + + -- Add up what we have. Could also be zero. + N=N+m + + -- Reduce number of available payloads. + Npayloads[cohort.aircrafttype]=Npayloads[cohort.aircrafttype]-m + end + + return N +end + --- Count assets on mission. -- @param #LEGION self -- @param #table MissionTypes Types on mission to be checked. Default all. @@ -1241,19 +1432,23 @@ function LEGION:CountAssetsOnMission(MissionTypes, Cohort) for _,_asset in pairs(mission.assets or {}) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + -- Ensure asset belongs to this letion. + if asset.wid==self.uid then - if Cohort==nil or Cohort.name==asset.squadname then - - local request, isqueued=self:GetRequestByID(mission.requestID[self.alias]) - - if isqueued then - Nq=Nq+1 - else - Np=Np+1 + if Cohort==nil or Cohort.name==asset.squadname then + + local request, isqueued=self:GetRequestByID(mission.requestID[self.alias]) + + if isqueued then + Nq=Nq+1 + else + Np=Np+1 + end + end - + end - end end end @@ -1279,8 +1474,13 @@ function LEGION:GetAssetsOnMission(MissionTypes) for _,_asset in pairs(mission.assets or {}) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + -- Ensure asset belongs to this legion. + if asset.wid==self.uid then - table.insert(assets, asset) + table.insert(assets, asset) + + end end end @@ -1289,7 +1489,7 @@ function LEGION:GetAssetsOnMission(MissionTypes) return assets end ---- Get the aircraft types of this airwing. +--- Get the unit types of this legion. These are the unit types of all assigned cohorts. -- @param #LEGION self -- @param #boolean onlyactive Count only the active ones. -- @param #table cohorts Table of cohorts. Default all. @@ -1332,24 +1532,24 @@ function LEGION:CanMission(Mission) -- Assume we CAN and NO assets are available. local Can=true local Assets={} - + -- Squadrons for the job. If user assigned to mission or simply all. local cohorts=Mission.squadrons or self.cohorts - local Nassets=Mission.nassets or 1 - if Mission.Nassets and Mission.Nassets[self.alias] then - Nassets=Mission.Nassets[self.alias] - end + -- Number of required assets. + local Nassets=Mission:GetRequiredAssets(self) -- Get aircraft unit types for the job. local unittypes=self:GetAircraftTypes(true, cohorts) -- Count all payloads in stock. if self:IsAirwing() then + + -- Number of payloads in stock. local Npayloads=self:CountPayloadsInStock(Mission.type, unittypes, Mission.payloads) if Npayloads false")) - return false - elseif self:IsStopped() then - self:T(self.lid..string.format("Onbefore Status STOPPED ==> false")) - return false - end - - return true -end - --- Update status. -- @param #NAVYGROUP self -function NAVYGROUP:onafterStatus(From, Event, To) +function NAVYGROUP:Status(From, Event, To) -- FSM state. local fsmstate=self:GetState() @@ -628,9 +614,6 @@ function NAVYGROUP:onafterStatus(From, Event, To) self:_PrintTaskAndMissionStatus() - - -- Next status update in 30 seconds. - self:__Status(-30) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -897,7 +880,7 @@ function NAVYGROUP:onafterTurnIntoWind(From, Event, To, IntoWind) IntoWind.waypoint=wptiw - if IntoWind.Uturn and self.Debug then + if IntoWind.Uturn and false then IntoWind.Coordinate:MarkToAll("Return coord") end @@ -1120,7 +1103,7 @@ function NAVYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Depth, Up -- Check if final waypoint is still passed. if wpnumber>self.currentwp then - self.passedfinalwp=false + self:_PassedFinalWaypoint(false, "NAVYGROUP:AddWaypoint wpnumber>self.currentwp") end -- Speed in knots. diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 194dac649..ebbc98861 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -14,7 +14,6 @@ --- OPSGROUP class. -- @type OPSGROUP -- @field #string ClassName Name of the class. --- @field #boolean Debug Debug mode. Messages to all about status. -- @field #number verbose Verbosity level. 0=silent. -- @field #string lid Class id string for output to DCS log file. -- @field #string groupname Name of the group. @@ -60,9 +59,9 @@ -- @field #number speedWp Speed to the next waypoint in m/s. -- @field #boolean passedfinalwp Group has passed the final waypoint. -- @field #number wpcounter Running number counting waypoints. --- @field #boolean respawning Group is being respawned. -- @field Core.Set#SET_ZONE checkzones Set of zones. -- @field Core.Set#SET_ZONE inzones Set of zones in which the group is currently in. +-- @field Core.Timer#TIMER timerStatus Timer for status update. -- @field Core.Timer#TIMER timerCheckZone Timer for check zones. -- @field Core.Timer#TIMER timerQueueUpdate Timer for queue updates. -- @field #boolean groupinitialized If true, group parameters were initialized. @@ -145,7 +144,6 @@ -- @field #OPSGROUP OPSGROUP = { ClassName = "OPSGROUP", - Debug = false, verbose = 0, lid = nil, groupname = nil, @@ -169,7 +167,6 @@ OPSGROUP = { checkzones = nil, inzones = nil, groupinitialized = nil, - respawning = nil, wpcounter = 1, radio = {}, option = {}, @@ -553,7 +550,7 @@ function OPSGROUP:New(group) self:AddTransition("*", "InUtero", "InUtero") -- Deactivated group goes back to mummy. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. - self:AddTransition("*", "Status", "*") -- Status update. + --self:AddTransition("*", "Status", "*") -- Status update. self:AddTransition("*", "Destroyed", "*") -- The whole group is dead. self:AddTransition("*", "Damaged", "*") -- Someone in the group took damage. @@ -580,6 +577,11 @@ function OPSGROUP:New(group) self:AddTransition("*", "OutOfRockets", "*") -- Group is out of rockets. self:AddTransition("*", "OutOfBombs", "*") -- Group is out of bombs. self:AddTransition("*", "OutOfMissiles", "*") -- Group is out of missiles. + self:AddTransition("*", "OutOfTorpedos", "*") -- Group is out of torpedos. + + self:AddTransition("*", "OutOfMissilesAA", "*") -- Group is out of A2A (air) missiles. + self:AddTransition("*", "OutOfMissilesAG", "*") -- Group is out of A2G (ground) missiles. + self:AddTransition("*", "OutOfMissilesAS", "*") -- Group is out of A2S (ship) missiles. self:AddTransition("*", "EnterZone", "*") -- Group entered a certain zone. self:AddTransition("*", "LeaveZone", "*") -- Group leaves a certain zone. @@ -2127,16 +2129,21 @@ end -- @return #number Next waypoint index. function OPSGROUP:GetWaypointIndexNext(cyclic, i) + -- If not specified, we take the adinititum value. if cyclic==nil then cyclic=self.adinfinitum end + -- Total number of waypoints. local N=#self.waypoints + -- Default is currentwp. i=i or self.currentwp + -- If no next waypoint exists, because the final waypoint was reached, we return the last waypoint. local n=math.min(i+1, N) + -- If last waypoint was reached, the first waypoint is the next in line. if cyclic and i==N then n=1 end @@ -2388,7 +2395,7 @@ function OPSGROUP:RemoveWaypoint(wpindex) -- TODO: patrol adinfinitum. if self.currentwp>=n then - self.passedfinalwp=true + self:_PassedFinalWaypoint(true, "Removed FUTURE waypoint") end self:_CheckGroupDone(1) @@ -3088,9 +3095,9 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) -- New waypoint. if self.isFlightgroup then FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) - elseif self.isNavygroup then - ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation) elseif self.isArmygroup then + ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation) + elseif self.isNavygroup then NAVYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) end @@ -3119,9 +3126,9 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) -- New waypoint. if self.isFlightgroup then FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) - elseif self.isNavygroup then - ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation) elseif self.isArmygroup then + ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation) + elseif self.isNavygroup then NAVYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) end @@ -3464,10 +3471,28 @@ function OPSGROUP:_GetNextMission() -- Look for first mission that is SCHEDULED. for _,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG + + -- Local transport. + local transport=true + if mission.opstransport then + local cargos=mission.opstransport:GetCargoOpsGroups(false) or {} + for _,_opsgroup in pairs(cargos) do + local opscargo=_opsgroup --Ops.OpsGroup#OPSGROUP + if opscargo.groupname==self.groupname then + transport=false + break + end + end + end + + -- TODO: One could think of opsgroup specific start conditions. A legion also checks if "ready" but it can be other criteria for the group to actually start the mission. + -- Good example is the above transport. The legion should start the mission but the group should only start after the transport is finished. - if mission:GetGroupStatus(self)==AUFTRAG.Status.SCHEDULED and (mission:IsReadyToGo() or self.legion) and (mission.importance==nil or mission.importance<=vip) then + -- 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 return mission end + end return nil @@ -3817,6 +3842,11 @@ function OPSGROUP:RouteToMission(mission, delay) if self:IsDead() or self:IsStopped() then return end + + if mission.type==AUFTRAG.Type.OPSTRANSPORT then + self:AddOpsTransport(mission.opstransport) + return + end -- ID of current waypoint. local uid=self:GetWaypointCurrent().uid @@ -4110,9 +4140,9 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) if self.isFlightgroup then FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) - elseif self.isNavygroup then - ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation) elseif self.isArmygroup then + ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation) + elseif self.isNavygroup then NAVYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) end @@ -4140,9 +4170,9 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) if self.isFlightgroup then FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) - elseif self.isNavygroup then - ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation) elseif self.isArmygroup then + ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation) + elseif self.isNavygroup then NAVYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) end @@ -4158,8 +4188,8 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) if wpindex==nil or wpindex==#self.waypoints then -- Set switch to true. - if not self.adinfinitum or #self.waypoints<=1 then - self.passedfinalwp=true + if not self.adinfinitum or #self.waypoints<=1 then + self:_PassedFinalWaypoint(true, "Passing waypoint and NOT adinfinitum and #self.waypoints<=1") end end @@ -4182,7 +4212,7 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) -- Set switch to true. if not self.adinfinitum or #self.waypoints<=1 then - self.passedfinalwp=true + self:_PassedFinalWaypoint(true, "PassingWaypoint: wpindex=nil or wpindex=#self.waypoints") end end @@ -4846,6 +4876,22 @@ function OPSGROUP:_UpdateLaser() end +--- On before "ElementSpawned" event. Check that element is not in status spawned already. +-- @param #FLIGHTGROUP 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. +function OPSGROUP:onbeforeElementSpawned(From, Event, To, Element) + + if Element and Element.status==OPSGROUP.ElementStatus.SPAWNED then + self:I(self.lid..string.format("FF element %s is already spawned", Element.name)) + return false + end + + return true +end + --- On after "ElementInUtero" event. -- @param #OPSGROUP self -- @param #string From From state. @@ -5254,6 +5300,7 @@ function OPSGROUP:onafterStop(From, Event, To) -- Stop check timers. self.timerCheckZone:Stop() self.timerQueueUpdate:Stop() + self.timerStatus:Stop() -- Stop FSM scheduler. self.CallScheduler:Clear() @@ -7518,7 +7565,16 @@ function OPSGROUP:_CheckAmmoStatus() if ammo.MissilesAS and self.ammo.MissilesAS>0 and not self.outofMissilesAS then self.outofMissilesAS=true self:OutOfMissilesAS() - end + end + + -- Torpedos. + if self.outofTorpedos and ammo.Torpedos>0 then + self.outofTorpedos=false + end + if ammo.Torpedos==0 and self.ammo.Torpedos>0 and not self.outofTorpedos then + self.outofTorpedos=true + self:OutOfTorpedos() + end -- Check if group is engaging. @@ -7647,7 +7703,7 @@ function OPSGROUP:_AddWaypoint(waypoint, wpnumber) -- Now we obviously did not pass the final waypoint. if self.currentwp and wpnumber>self.currentwp then - self.passedfinalwp=false + self:_PassedFinalWaypoint(false, "_AddWaypoint self.currentwp and wpnumber>self.currentwp") end end @@ -7733,7 +7789,7 @@ function OPSGROUP:_InitWaypoints(WpIndexMin, WpIndexMax) -- Check if only 1 wp? if #self.waypoints==1 then - self.passedfinalwp=true + self:_PassedFinalWaypoint(true, "_InitWaypoints: #self.waypoints==1") end else @@ -7825,41 +7881,62 @@ end --@param #number uid Waypoint UID. function OPSGROUP._PassingWaypoint(group, opsgroup, uid) + -- Debug message. + local text=string.format("Group passing waypoint uid=%d", uid) + opsgroup:T(opsgroup.lid..text) + -- Get waypoint data. local waypoint=opsgroup:GetWaypointByID(uid) if waypoint then + + -- Increase passing counter. + waypoint.npassed=waypoint.npassed+1 -- Current wp. local currentwp=opsgroup.currentwp -- Get the current waypoint index. opsgroup.currentwp=opsgroup:GetWaypointIndex(uid) + + local wpistemp=waypoint.temp or waypoint.detour or waypoint.astar + + -- Remove temp waypoints. + if wpistemp then + opsgroup:RemoveWaypointByID(uid) + end - -- Set expected speed and formation from the next WP. + -- Get next waypoint. Tricky part is that if local wpnext=opsgroup:GetWaypointNext() - if wpnext then + + if wpnext and (opsgroup.currentwp<#opsgroup.waypoints or opsgroup.adinfinitum or wpistemp) then + + opsgroup:I(opsgroup.lid..string.format("Next waypoint UID=%d index=%d", wpnext.uid, opsgroup:GetWaypointIndex(wpnext.uid))) -- Set formation. if opsgroup.isGround then opsgroup.formation=wpnext.action end - -- Set speed. + -- Set speed to next wp. opsgroup.speed=wpnext.speed - + + if opsgroup.speed<0.01 then + opsgroup.speed=UTILS.KmphToMps(opsgroup.speedCruise) + end + + else + + env.info(opsgroup.lid.."FF 300") + + -- Set passed final waypoint. + opsgroup:_PassedFinalWaypoint(true, "_PassingWaypoint No next Waypoint found") + end - -- Debug message. - local text=string.format("Group passing waypoint uid=%d", uid) - opsgroup:T(opsgroup.lid..text) - -- Trigger PassingWaypoint event. if waypoint.temp then - -- Remove temp waypoint. - opsgroup:RemoveWaypointByID(uid) - if opsgroup:IsNavygroup() or opsgroup:IsArmygroup() then --TODO: not sure if this works with FLIGHTGROUPS opsgroup:Cruise() @@ -7867,17 +7944,11 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) elseif waypoint.astar then - -- Remove Astar waypoint. - opsgroup:RemoveWaypointByID(uid) - -- Cruise. opsgroup:Cruise() elseif waypoint.detour then - -- Remove detour waypoint. - opsgroup:RemoveWaypointByID(uid) - if opsgroup:IsRearming() then -- Trigger Rearming event. @@ -7890,6 +7961,7 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) elseif opsgroup:IsReturning() then + -- Trigger Returned event. opsgroup:Returned() elseif opsgroup:IsPickingup() then @@ -7965,9 +8037,6 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) opsgroup.ispathfinding=false end - -- Increase passing counter. - waypoint.npassed=waypoint.npassed+1 - -- Call event function. opsgroup:PassingWaypoint(waypoint) end @@ -9398,7 +9467,7 @@ function OPSGROUP:GetAmmoUnit(unit, display) nmissilesAS=nmissilesAS+Nammo elseif MissileCategory==Weapon.MissileCategory.BM then nmissiles=nmissiles+Nammo - nmissilesAG=nmissilesAG+Nammo + nmissilesBM=nmissilesBM+Nammo elseif MissileCategory==Weapon.MissileCategory.CRUISE then nmissiles=nmissiles+Nammo nmissilesCR=nmissilesCR+Nammo @@ -9477,6 +9546,20 @@ function OPSGROUP:_MissileCategoryName(categorynumber) return cat end +--- Set passed final waypoint value. +-- @param #OPSGROUP self +-- @param #boolean final If `true`, final waypoint was passed. +-- @param #string comment Some comment as to why the final waypoint was passed. +function OPSGROUP:_PassedFinalWaypoint(final, comment) + + -- Debug info. + self:I(self.lid..string.format("Passed final waypoint=%s [from %s]: comment \"%s\"", tostring(final), tostring(self.passedfinalwp), tostring(comment))) + + -- Set value. + self.passedfinalwp=final +end + + --- Get coordinate from an object. -- @param #OPSGROUP self -- @param Wrapper.Object#OBJECT Object The object. diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index b6a2e029c..6cdbf7430 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -119,16 +119,21 @@ OPSTRANSPORT = { pathsTransport = {}, pathsPickup = {}, requiredCargos = {}, + assets = {}, } --- Cargo transport status. -- @type OPSTRANSPORT.Status -- @field #string PLANNED Planning state. +-- @field #string QUEUED Queued state. +-- @field #string REQUESTED Requested state. -- @field #string SCHEDULED Transport is scheduled in the cargo queue. -- @field #string EXECUTING Transport is being executed. -- @field #string DELIVERED Transport was delivered. OPSTRANSPORT.Status={ PLANNED="planned", + QUEUED="queued", + REQUESTED="requested", SCHEDULED="scheduled", EXECUTING="executing", DELIVERED="delivered", @@ -207,7 +212,10 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) -- PLANNED --> SCHEDULED --> EXECUTING --> DELIVERED self:AddTransition("*", "Planned", OPSTRANSPORT.Status.PLANNED) -- Cargo transport was planned. - self:AddTransition(OPSTRANSPORT.Status.PLANNED, "Scheduled", OPSTRANSPORT.Status.SCHEDULED) -- Cargo is queued at at least one carrier. + self:AddTransition(OPSTRANSPORT.Status.PLANNED, "Queued", OPSTRANSPORT.Status.QUEUED) -- Cargo is queued at at least one carrier. + self:AddTransition(OPSTRANSPORT.Status.QUEUED, "Requested", OPSTRANSPORT.Status.REQUESTED) -- Transport assets have been requested from a warehouse. + self:AddTransition(OPSTRANSPORT.Status.QUEUED, "Scheduled", OPSTRANSPORT.Status.SCHEDULED) -- Cargo is queued at at least one carrier. + self:AddTransition(OPSTRANSPORT.Status.PLANNED, "Scheduled", OPSTRANSPORT.Status.SCHEDULED) -- Cargo is queued at at least one carrier. self:AddTransition(OPSTRANSPORT.Status.SCHEDULED, "Executing", OPSTRANSPORT.Status.EXECUTING) -- Cargo is being transported. self:AddTransition("*", "Delivered", OPSTRANSPORT.Status.DELIVERED) -- Cargo was delivered. @@ -814,6 +822,41 @@ function OPSTRANSPORT:IsReadyToGo() return true end +--- Check if state is PLANNED. +-- @param #OPSTRANSPORT self +-- @return #boolean If true, status is PLANNED. +function OPSTRANSPORT:IsPlanned() + return self:is(OPSTRANSPORT.Status.PLANNED) +end + +--- Check if state is QUEUED. +-- @param #OPSTRANSPORT self +-- @return #boolean If true, status is QUEUED. +function OPSTRANSPORT:IsQueued() + return self:is(OPSTRANSPORT.Status.QUEUED) +end + +--- Check if state is REQUESTED. +-- @param #OPSTRANSPORT self +-- @return #boolean If true, status is REQUESTED. +function OPSTRANSPORT:IsRequested() + return self:is(OPSTRANSPORT.Status.REQUESTED) +end + +--- Check if state is SCHEDULED. +-- @param #OPSTRANSPORT self +-- @return #boolean If true, status is SCHEDULED. +function OPSTRANSPORT:IsScheduled() + return self:is(OPSTRANSPORT.Status.SCHEDULED) +end + +--- Check if state is EXECUTING. +-- @param #OPSTRANSPORT self +-- @return #boolean If true, status is EXECUTING. +function OPSTRANSPORT:IsExecuting() + return self:is(OPSTRANSPORT.Status.EXECUTING) +end + --- Check if all cargo was delivered (or is dead). -- @param #OPSTRANSPORT self -- @return #boolean If true, all possible cargo was delivered.