- Lots of stuff
This commit is contained in:
Frank 2021-08-21 00:58:28 +02:00
parent 16964520df
commit d73ebaca76
10 changed files with 731 additions and 301 deletions

View File

@ -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.

View File

@ -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
-------------------------

View File

@ -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 ---
--------------

View File

@ -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".

View File

@ -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

View File

@ -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

View File

@ -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<Nassets then
self:T(self.lid..string.format("INFO: Not enough PAYLOADS available! Got %d but need at least %d", Npayloads, Mission.nassets))
self:T(self.lid..string.format("INFO: Not enough PAYLOADS available! Got %d but need at least %d", Npayloads, Nassets))
return false, Assets
end
end
@ -1493,6 +1693,24 @@ function LEGION:GetMissionByID(mid)
return nil
end
--- Returns the mission for a given ID.
-- @param #LEGION self
-- @param #number uid Transport UID.
-- @return Ops.OpsTransport#OPSTRANSPORT Transport assignment.
function LEGION:GetTransportByID(uid)
for _,_transport in pairs(self.transportqueue) do
local transport=_transport --Ops.OpsTransport#OPSTRANSPORT
if transport.uid==tonumber(uid) then
return transport
end
end
return nil
end
--- Returns the mission for a given request ID.
-- @param #LEGION self
-- @param #number RequestID Unique ID of the request.

View File

@ -88,7 +88,8 @@ NAVYGROUP.version="0.7.0"
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Extend, shorten turn into wind windows
-- TODO: Submaries.
-- TODO: Extend, shorten turn into wind windows.
-- TODO: Skipper menu.
-- DONE: Collision warning.
-- DONE: Detour, add temporary waypoint and resume route.
@ -176,7 +177,7 @@ function NAVYGROUP:New(group)
self:HandleEvent(EVENTS.RemoveUnit, self.OnEventRemoveUnit)
-- 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)
@ -454,24 +455,9 @@ end
-- Status
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
---- Update status.
-- @param #NAVYGROUP self
function NAVYGROUP: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 #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.

View File

@ -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.

View File

@ -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.