From 9b3f2ae3c73bd89d09022d0619169e6eb08651e6 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 31 May 2022 22:54:37 +0200 Subject: [PATCH] OPS --- Moose Development/Moose/Core/Condition.lua | 24 +- Moose Development/Moose/Ops/Auftrag.lua | 66 +++- Moose Development/Moose/Ops/Chief.lua | 42 +-- Moose Development/Moose/Ops/Cohort.lua | 17 +- Moose Development/Moose/Ops/Commander.lua | 210 +++++++---- Moose Development/Moose/Ops/FlightControl.lua | 94 +++-- Moose Development/Moose/Ops/FlightGroup.lua | 5 +- Moose Development/Moose/Ops/Legion.lua | 2 +- Moose Development/Moose/Ops/Operation.lua | 326 ++++++++++++++++-- Moose Development/Moose/Ops/OpsGroup.lua | 3 + Moose Development/Moose/Ops/Target.lua | 1 + 11 files changed, 580 insertions(+), 210 deletions(-) diff --git a/Moose Development/Moose/Core/Condition.lua b/Moose Development/Moose/Core/Condition.lua index d64b8c727..7c25f1948 100644 --- a/Moose Development/Moose/Core/Condition.lua +++ b/Moose Development/Moose/Core/Condition.lua @@ -7,10 +7,17 @@ -- -- === -- +-- ## Example Missions: +-- +-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20Operation). +-- +-- === +-- -- ### Author: **funkyfranky** +-- +-- === -- @module Core.Condition --- @image Core_Condition.png - +-- @image Core_Conditon.png --- CONDITON class. -- @type CONDITION @@ -48,7 +55,7 @@ CONDITION = { --- CONDITION class version. -- @field #string version -CONDITION.version="0.0.1" +CONDITION.version="0.1.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -157,6 +164,15 @@ end -- @return #boolean Result of condition functions. function CONDITION:Evaluate(AnyTrue) + -- Check if at least one function was given. + if #self.functionsAll + #self.functionsAny + #self.functionsAll == 0 then + if self.negateResult then + return true + else + return false + end + end + -- Any condition for gen. local evalAny=self.isAny if AnyTrue~=nil then @@ -185,7 +201,7 @@ function CONDITION:Evaluate(AnyTrue) end -- Debug message. - self:I(self.lid..string.format("Evaluate: isGen=%s, isAny=%s, isAll=%s (negate=%s) ==> result=%s", tostring(isGen), tostring(isAny), tostring(isAll), tostring(self.negateResult), tostring(result))) + self:T(self.lid..string.format("Evaluate: isGen=%s, isAny=%s, isAll=%s (negate=%s) ==> result=%s", tostring(isGen), tostring(isAny), tostring(isAll), tostring(self.negateResult), tostring(result))) return result end diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index f03539d27..e9ae00128 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -72,6 +72,8 @@ -- -- @field Ops.Target#TARGET engageTarget Target data to engage. -- +-- @field Ops.Operation#OPERATION operation Operation this mission is part of. +-- -- @field #boolean teleport Groups are teleported to the mission ingress waypoint. -- -- @field Core.Zone#ZONE_RADIUS engageZone *Circular* engagement zone. @@ -692,6 +694,9 @@ function AUFTRAG:New(Type) self.Ncasualties=0 self.Nkills=0 self.Nelements=0 + self.Ngroups=0 + self.Nassigned=nil + self.Ndead=0 -- FMS start state is PLANNED. self:SetStartState(self.status) @@ -3340,9 +3345,33 @@ end --- Check if mission is EXECUTING. The first OPSGROUP has reached the mission execution waypoint and is not executing the mission task. -- @param #AUFTRAG self +-- @param #boolean AllGroups (Optional) Check that all groups are currently executing the mission. -- @return #boolean If true, mission is currently executing. -function AUFTRAG:IsExecuting() - return self.status==AUFTRAG.Status.EXECUTING +function AUFTRAG:IsExecuting(AllGroups) + + local isExecuting=self.status==AUFTRAG.Status.EXECUTING + + if AllGroups and isExecuting then + + -- Number of groups executing. + local n=self:CountOpsGroupsInStatus(AUFTRAG.GroupStatus.EXECUTING) + + local N + if self.Nassigned then + N=self.Nassigned-self.Ndead + else + N=self:CountOpsGroups() + end + + if n==N then + return true + else + return false + end + + end + + return isExecuting end --- Check if mission was cancelled. @@ -4301,7 +4330,8 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Ops.OpsGroup#OPSGROUP OpsGroup The ops group that is dead now. +-- @param Ops.OpsGroup#OPSGROUP OpsGroup The ops group to which the element belongs. +-- @param Ops.OpsGroup#OPSGROUP.Element Element The element that got destroyed. function AUFTRAG:onafterElementDestroyed(From, Event, To, OpsGroup, Element) -- Increase number of own casualties. self.Ncasualties=self.Ncasualties+1 @@ -4319,6 +4349,9 @@ function AUFTRAG:onafterGroupDead(From, Event, To, OpsGroup) if asset then self:AssetDead(asset) end + + -- Number of dead groups. + self.Ndead=self.Ndead+1 end @@ -4677,6 +4710,9 @@ function AUFTRAG:onafterRepeat(From, Event, To) -- Reset casualties and units assigned. self.Ncasualties=0 self.Nelements=0 + self.Ngroups=0 + self.Nassigned=nil + self.Ndead=0 -- Update DCS mission task. Could be that the initial task (e.g. for bombing) was destroyed. Then we need to update the coordinate. self.DCStask=self:GetDCSMissionTask() @@ -4949,12 +4985,18 @@ function AUFTRAG:AddAsset(Asset) -- Add to table. self.assets=self.assets or {} + + -- Add to table. table.insert(self.assets, Asset) + + self.Nassigned=self.Nassigned or 0 + + self.Nassigned=self.Nassigned+1 return self end ---- Add asset to mission. +--- Add assets to mission. -- @param #AUFTRAG self -- @param #table Assets List of assets. -- @return #AUFTRAG self @@ -5019,6 +5061,22 @@ function AUFTRAG:CountOpsGroups() return N end +--- Count OPS groups in a certain status. +-- @param #AUFTRAG self +-- @param #string Status Status of group, e.g. `AUFTRAG.GroupStatus.EXECUTING`. +-- @return #number Number of alive OPS groups. +function AUFTRAG:CountOpsGroupsInStatus(Status) + local N=0 + for _,_groupdata in pairs(self.groupdata) do + local groupdata=_groupdata --#AUFTRAG.GroupData + if groupdata and groupdata.status==Status then + N=N+1 + end + end + return N +end + + --- Get coordinate of target. First unit/group of the set is used. -- @param #AUFTRAG self diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index 006fad90d..34a188fe4 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -2128,10 +2128,8 @@ function CHIEF:CheckTargetQueue() -- Add asset to mission. if mission then - for _,_asset in pairs(assets) do - local asset=_asset - mission:AddAsset(asset) - end + + mission:_AddAssets(assets) Legions=legions -- We got what we wanted ==> leave loop. @@ -2663,23 +2661,7 @@ end function CHIEF:RecruitAssetsForTarget(Target, MissionType, NassetsMin, NassetsMax) -- Cohorts. - local Cohorts={} - for _,_legion in pairs(self.commander.legions) do - local legion=_legion --Ops.Legion#LEGION - - -- Check that runway is operational.d - local Runway=legion:IsAirwing() and legion:IsRunwayOperational() or true - - if legion:IsRunning() and Runway then - - -- Loops over cohorts. - for _,_cohort in pairs(legion.cohorts) do - local cohort=_cohort --Ops.Cohort#COHORT - table.insert(Cohorts, cohort) - end - - end - end + local Cohorts=self.commander:_GetCohorts() -- Target position. local TargetVec2=Target:GetVec2() @@ -2699,23 +2681,7 @@ end function CHIEF:RecruitAssetsForZone(StratZone, Resource) -- Cohorts. - local Cohorts={} - for _,_legion in pairs(self.commander.legions) do - local legion=_legion --Ops.Legion#LEGION - - -- Check that runway is operational. - local Runway=legion:IsAirwing() and legion:IsRunwayOperational() or true - - if legion:IsRunning() and Runway then - - -- Loops over cohorts. - for _,_cohort in pairs(legion.cohorts) do - local cohort=_cohort --Ops.Cohort#COHORT - table.insert(Cohorts, cohort) - end - - end - end + local Cohorts=self.commander:_GetCohorts() -- Shortcuts. local MissionType=Resource.MissionType diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index 4ffcf6c01..be05e288a 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -242,8 +242,8 @@ function COHORT:New(TemplateGroupName, Ngroups, CohortName) -- @param #number delay Delay in seconds. --- On after "Pause" event. - -- @function [parent=#AUFTRAG] OnAfterPause - -- @param #AUFTRAG self + -- @function [parent=#COHORT] OnAfterPause + -- @param #COHORT self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. @@ -259,8 +259,8 @@ function COHORT:New(TemplateGroupName, Ngroups, CohortName) -- @param #number delay Delay in seconds. --- On after "Unpause" event. - -- @function [parent=#AUFTRAG] OnAfterUnpause - -- @param #AUFTRAG self + -- @function [parent=#COHORT] OnAfterUnpause + -- @param #COHORT self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. @@ -276,8 +276,8 @@ function COHORT:New(TemplateGroupName, Ngroups, CohortName) -- @param #number delay Delay in seconds. --- On after "Relocate" event. - -- @function [parent=#AUFTRAG] OnAfterRelocate - -- @param #AUFTRAG self + -- @function [parent=#COHORT] OnAfterRelocate + -- @param #COHORT self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. @@ -293,8 +293,8 @@ function COHORT:New(TemplateGroupName, Ngroups, CohortName) -- @param #number delay Delay in seconds. --- On after "Relocated" event. - -- @function [parent=#AUFTRAG] OnAfterRelocated - -- @param #AUFTRAG self + -- @function [parent=#COHORT] OnAfterRelocated + -- @param #COHORT self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. @@ -1004,7 +1004,6 @@ function COHORT:GetOpsGroups(MissionTypes, Attributes) if MissionTypes==nil or AUFTRAG.CheckMissionCapability(MissionTypes, self.missiontypes) then if Attributes==nil or self:CheckAttribute(Attributes) then if asset.flightgroup and asset.flightgroup:IsAlive() then - --set:AddObject(asset.flightgroup) set:AddGroup(asset.flightgroup) end end diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index 3c4c18503..3b0e10a60 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -531,6 +531,20 @@ function COMMANDER:AddTarget(Target) return self end +--- Add operation. +-- @param #COMMANDER self +-- @param Ops.Operation#OPERATION Operation The operation to be added. +-- @return #COMMANDER self +function COMMANDER:AddOperation(Operation) + + -- TODO: Check that is not already added. + + -- Add operation to table. + table.insert(self.opsqueue, Operation) + + return self +end + --- Check if a TARGET is already in the queue. -- @param #COMMANDER self -- @param Ops.Target#TARGET Target Target object to be added. @@ -1236,9 +1250,12 @@ function COMMANDER:CheckOpsQueue() -- Loop over operations. for _,_ops in pairs(self.opsqueue) do - local operation=_ops --Ops.Operation#OPRATION + local operation=_ops --Ops.Operation#OPERATION - --TODO: What? + if operation:IsRunning() then + + + end end @@ -1408,10 +1425,7 @@ function COMMANDER:CheckMissionQueue() if recruited then -- Add asset to mission. - for _,_asset in pairs(assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - mission:AddAsset(asset) - end + mission:_AddAssets(assets) -- Recruit asset for escorting recruited mission assets. local EscortAvail=self:RecruitAssetsForEscort(mission, assets) @@ -1467,6 +1481,121 @@ function COMMANDER:CheckMissionQueue() end +--- Get cohorts. +-- @param #COMMANDER self +-- @param #table Legions Special legions. +-- @param #table Cohorts Special cohorts. +-- @param Ops.Operation#OPERATION Operation Operation. +-- @return #table Cohorts. +function COMMANDER:_GetCohorts(Legions, Cohorts, Operation) + + --- Function that check if a legion or cohort is part of an operation. + local function CheckOperation(LegionOrCohort) + -- No operations ==> no problem! + if #self.opsqueue==0 then + return true + end + + -- Cohort is not dedicated to a running(!) operation. We assume so. + local isAvail=true + + -- Only available... + if Operation then + isAvail=false + end + + for _,_operation in pairs(self.opsqueue) do + local operation=_operation --Ops.Operation#OPERATION + + -- Legion is assigned to this operation. + local isOps=operation:IsAssignedCohortOrLegion(LegionOrCohort) + + if isOps and operation:IsRunning() then + + -- Is dedicated. + isAvail=false + + if Operation==nil then + -- No Operation given and this is dedicated to at least one operation. + return false + else + if Operation.uid==operation.uid then + -- Operation given and is part of it. + return true + end + end + end + end + + return isAvail + end + + -- Chosen cohorts. + local cohorts={} + + -- Check if there are any special legions and/or cohorts. + if (Legions and #Legions>0) or (Cohorts and #Cohorts>0) then + + -- Add cohorts of special legions. + for _,_legion in pairs(Legions or {}) do + local legion=_legion --Ops.Legion#LEGION + + -- Check that runway is operational. + local Runway=legion:IsAirwing() and legion:IsRunwayOperational() or true + + -- Legion has to be running. + if legion:IsRunning() and Runway then + + -- Add cohorts of legion. + for _,_cohort in pairs(legion.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + + if CheckOperation(cohort.legion) or CheckOperation(cohort) then + table.insert(cohorts, cohort) + end + end + + end + end + + -- Add special cohorts. + for _,_cohort in pairs(Cohorts or {}) do + local cohort=_cohort --Ops.Cohort#COHORT + + if CheckOperation(cohort) then + table.insert(cohorts, cohort) + end + end + + else + + -- No special mission legions/cohorts found ==> take own legions. + for _,_legion in pairs(self.legions) do + local legion=_legion --Ops.Legion#LEGION + + -- Check that runway is operational. + local Runway=legion:IsAirwing() and legion:IsRunwayOperational() or true + + -- Legion has to be running. + if legion:IsRunning() and Runway then + + -- Add cohorts of legion. + for _,_cohort in pairs(legion.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + + if CheckOperation(cohort.legion) or CheckOperation(cohort) then + table.insert(cohorts, cohort) + end + end + + end + end + + end + + return cohorts +end + --- Recruit assets for a given mission. -- @param #COMMANDER self -- @param Ops.Auftrag#AUFTRAG Mission The mission. @@ -1479,29 +1608,10 @@ function COMMANDER:RecruitAssetsForMission(Mission) self:T2(self.lid..string.format("Recruiting assets for mission \"%s\" [%s]", Mission:GetName(), Mission:GetType())) -- Cohorts. - local Cohorts={} - for _,_legion in pairs(Mission.specialLegions or {}) do - local legion=_legion --Ops.Legion#LEGION - for _,_cohort in pairs(legion.cohorts) do - local cohort=_cohort --Ops.Cohort#COHORT - table.insert(Cohorts, cohort) - end - end - for _,_cohort in pairs(Mission.specialCohorts or {}) do - local cohort=_cohort --Ops.Cohort#COHORT - table.insert(Cohorts, cohort) - end + local Cohorts=self:_GetCohorts(Mission.specialLegions, Mission.specialCohorts, Mission.operation) - -- No special mission legions/cohorts found ==> take own legions. - if #Cohorts==0 then - for _,_legion in pairs(self.legions) do - local legion=_legion --Ops.Legion#LEGION - for _,_cohort in pairs(legion.cohorts) do - local cohort=_cohort --Ops.Cohort#COHORT - table.insert(Cohorts, cohort) - end - end - end + -- Debug info. + self:T(self.lid..string.format("Found %d cohort candidates for mission", #Cohorts)) -- Number of required assets. local NreqMin, NreqMax=Mission:GetRequiredAssets() @@ -1530,30 +1640,7 @@ function COMMANDER:RecruitAssetsForEscort(Mission, Assets) if Mission.NescortMin and Mission.NescortMax and (Mission.NescortMin>0 or Mission.NescortMax>0) then -- Cohorts. - local Cohorts={} - for _,_legion in pairs(Mission.escortLegions or {}) do - local legion=_legion --Ops.Legion#LEGION - for _,_cohort in pairs(legion.cohorts) do - local cohort=_cohort --Ops.Cohort#COHORT - table.insert(Cohorts, cohort) - end - end - for _,_cohort in pairs(Mission.escortCohorts or {}) do - local cohort=_cohort --Ops.Cohort#COHORT - table.insert(Cohorts, cohort) - end - - -- No special escort legions/cohorts found ==> take own legions. - if #Cohorts==0 then - for _,_legion in pairs(self.legions) do - local legion=_legion --Ops.Legion#LEGION - for _,_cohort in pairs(legion.cohorts) do - local cohort=_cohort --Ops.Cohort#COHORT - table.insert(Cohorts, cohort) - end - end - end - + local Cohorts=self:_GetCohorts(Mission.escortLegions, Mission.escortCohorts, Mission.operation) -- Call LEGION function but provide COMMANDER as self. local assigned=LEGION.AssignAssetsForEscort(self, Cohorts, Assets, Mission.NescortMin, Mission.NescortMax, Mission.escortTargetTypes, Mission.escortEngageRange) @@ -1683,24 +1770,7 @@ function COMMANDER:RecruitAssetsForTransport(Transport, CargoWeight, TotalWeight end -- Cohorts. - local Cohorts={} - for _,_legion in pairs(self.legions) do - local legion=_legion --Ops.Legion#LEGION - - -- Check that runway is operational. - local Runway=legion:IsAirwing() and legion:IsRunwayOperational() or true - - if legion:IsRunning() and Runway then - - -- Loops over cohorts. - for _,_cohort in pairs(legion.cohorts) do - local cohort=_cohort --Ops.Cohort#COHORT - table.insert(Cohorts, cohort) - end - - end - end - + local Cohorts=self:_GetCohorts() -- Target is the deploy zone. local TargetVec2=Transport:GetDeployZone():GetVec2() diff --git a/Moose Development/Moose/Ops/FlightControl.lua b/Moose Development/Moose/Ops/FlightControl.lua index 039541019..1ae08735e 100644 --- a/Moose Development/Moose/Ops/FlightControl.lua +++ b/Moose Development/Moose/Ops/FlightControl.lua @@ -137,6 +137,7 @@ FLIGHTCONTROL = { --- Flight status. -- @type FLIGHTCONTROL.FlightStatus +-- @field #string UNKNOWN Flight is unknown. -- @field #string INBOUND Flight is inbound. -- @field #string HOLDING Flight is holding. -- @field #string LANDING Flight is landing. @@ -147,6 +148,7 @@ FLIGHTCONTROL = { -- @field #string READYTO Flight is ready for takeoff. -- @field #string TAKEOFF Flight is taking off. FLIGHTCONTROL.FlightStatus={ + UNKNOWN="Unknown", PARKING="Parking", READYTX="Ready To Taxi", TAXIOUT="Taxi To Runway", @@ -1277,39 +1279,6 @@ function FLIGHTCONTROL:_PrintQueue(queue, name) return text end ---- Remove a flight group from a queue. --- @param #FLIGHTCONTROL self --- @param #table queue The queue from which the group will be removed. --- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group that will be removed from queue. --- @param #string queuename Name of the queue. --- @return #boolean True, flight was in Queue and removed. False otherwise. --- @return #number Table index of removed queue element or nil. -function FLIGHTCONTROL:_RemoveFlightFromQueue(queue, flight, queuename) - - queuename=queuename or "unknown" - - -- Loop over all flights in group. - for i,_flight in pairs(queue) do - local qflight=_flight --Ops.FlightGroup#FLIGHTGROUP - - -- Check for name. - if qflight.groupname==flight.groupname then - self:T(self.lid..string.format("Removing flight group %s from %s queue", flight.groupname, queuename)) - table.remove(queue, i) - - if not flight.isAI then - flight:_UpdateMenu(0.5) - end - - return true, i - end - end - - self:E(self.lid..string.format("WARNING: Could NOT remove flight group %s from %s queue", flight.groupname, queuename)) - return false, nil -end - - --- Set flight status. -- @param #FLIGHTCONTROL self -- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group. @@ -2158,15 +2127,6 @@ function FLIGHTCONTROL:_PlayerRequestInbound(groupname) if flight then if flight:IsAirborne() then - - if self:IsControlling(flight) then - -- Nothing to do as this flight has already the right flightcontrol. - else - - -- Set FC controlling this flight. - flight:SetFlightControl(self) - - end -- Call sign. local callsign=flight:GetCallsignName() @@ -2216,10 +2176,7 @@ function FLIGHTCONTROL:_PlayerRequestInbound(groupname) -- Send message. self:TransmissionTower(text, flight, 15) - - -- Create player menu. - --flight:_UpdateMenu() - + else self:E(self.lid..string.format("WARNING: Could not get holding stack for flight %s", flight:GetName())) end @@ -2259,6 +2216,7 @@ function FLIGHTCONTROL:_PlayerVectorInbound(groupname) if flight then + -- Check if inbound, controlled and have a stack. if flight:IsInbound() and self:IsControlling(flight) and flight.stack then -- Call sign. @@ -2279,9 +2237,10 @@ function FLIGHTCONTROL:_PlayerVectorInbound(groupname) -- Heading to holding point. local heading=flightcoord:HeadingTo(flight.stack.pos0) - -- Distance to holding point. + -- Distance to holding point in meters. local distance=flightcoord:Get2DDistance(flight.stack.pos0) + -- Distance in NM. local dist=UTILS.MetersToNM(distance) -- Message text. @@ -2291,6 +2250,10 @@ function FLIGHTCONTROL:_PlayerVectorInbound(groupname) -- Send message. self:TextMessageToFlight(text, flight) + else + -- Send message. + local text="Negative, you must be INBOUND, CONTROLLED by us and have an assigned STACK!" + self:TextMessageToFlight(text, flight) end else self:E(self.lid..string.format("Cannot find flight group %s.", tostring(groupname))) @@ -3034,13 +2997,38 @@ end --- Remove flight from all queues. -- @param #FLIGHTCONTROL self --- @param Ops.FlightGroup#FLIGHTGROUP flight The flight to be removed. -function FLIGHTCONTROL:_RemoveFlight(flight) - - flight.flightcontrol=nil - - self:_RemoveFlightFromQueue(self.flights, flight, "flights") +-- @param Ops.FlightGroup#FLIGHTGROUP Flight The flight to be removed. +function FLIGHTCONTROL:_RemoveFlight(Flight) + -- Loop over all flights in group. + for i,_flight in pairs(self.flights) do + local flight=_flight --Ops.FlightGroup#FLIGHTGROUP + + -- Check for name. + if flight.groupname==Flight.groupname then + + -- Debug message. + self:T(self.lid..string.format("Removing flight group %s", flight.groupname)) + + -- Remove table entry. + table.remove(self.flights, i) + + -- Remove myself. + Flight.flightcontrol=nil + + -- Set flight status to unknown. + self:SetFlightStatus(Flight, FLIGHTCONTROL.FlightStatus.UNKNOWN) + + -- Update menu. + if not flight.isAI then + flight:_UpdateMenu(0.5) + end + + end + end + + -- + self:E(self.lid..string.format("WARNING: Could NOT remove flight group %s from %s queue", flight.groupname, queuename)) end --- Get flight from group. diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 65b8ae44e..8c485d905 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -412,7 +412,7 @@ function FLIGHTGROUP:SetFlightControl(flightcontrol) end -- Update flight's F10 menu. - if self.isAI==false then + if not self.isAI then self:_UpdateMenu(0.5) end @@ -2447,6 +2447,7 @@ function FLIGHTGROUP:onafterRTB(From, Event, To, airbase, SpeedTo, SpeedHold, Sp end + -- Land at airbase. self:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) end @@ -4173,7 +4174,7 @@ function FLIGHTGROUP:_UpdateMenu(delay) -- Message to group. MESSAGE:New("Updating MENU state="..self:GetState(), 5):ToGroup(self.group) - env.info(self.lid.."updating menu state=") + env.info(self.lid.."updating menu state="..self:GetState()) -- Player element. local player=self:GetPlayerElement() diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 027aba19e..150384c15 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -1609,7 +1609,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #WAREHOUSE.Pendingitem Request Information table of the request. +-- @param Functional.Warehouse#WAREHOUSE.Pendingitem Request Information table of the request. -- @param Core.Set#SET_GROUP CargoGroupSet Set of cargo groups. -- @param Core.Set#SET_GROUP TransportGroupSet Set of transport groups if any. function LEGION:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet, TransportGroupSet) diff --git a/Moose Development/Moose/Ops/Operation.lua b/Moose Development/Moose/Ops/Operation.lua index d1ea5aa83..71bed062a 100644 --- a/Moose Development/Moose/Ops/Operation.lua +++ b/Moose Development/Moose/Ops/Operation.lua @@ -3,6 +3,7 @@ -- ## Main Features: -- -- * Define operation phases +-- * Define conditions when phases are over -- * Dedicate resources to operations -- -- === @@ -31,6 +32,7 @@ -- @field #table phases Phases. -- @field #number counterPhase Running number counting the phases. -- @field #OPERATION.Phase phase Currently active phase (if any). +-- @field #table targets Targets. -- -- @extends Core.Fsm#FSM @@ -51,6 +53,7 @@ OPERATION = { legions = {}, phases = {}, counterPhase = 0, + targets = {}, } --- Global mission counter. @@ -61,7 +64,18 @@ _OPERATIONID=0 -- @field #number uid Unique ID of the phase. -- @field #string name Name of the phase. -- @field Core.Condition#CONDITION conditionOver Conditions when the phase is over. --- @field #boolean isOver If `true`, phase is over. +-- @field #string status Phase status. + +--- Operation phase. +-- @type OPERATION.PhaseStatus +-- @field #string PLANNED Planned. +-- @field #string ACTIVE Active phase. +-- @field #string OVER Phase is over. +OPERATION.PhaseStatus={ + PLANNED="Planned", + ACTIVE="Active", + OVER="Over", +} --- OPERATION class version. -- @field #string version @@ -89,13 +103,15 @@ function OPERATION:New(Name) -- Increase global counter. _OPERATIONID=_OPERATIONID+1 + -- Unique ID of the operation. + self.uid=_OPERATIONID + -- Set Name. self.name=Name or string.format("Operation-%02d", _OPERATIONID) -- Set log ID. self.lid=string.format("%s | ",self.name) - -- FMS start state is PLANNED. self:SetStartState("Planned") @@ -108,7 +124,7 @@ function OPERATION:New(Name) self:AddTransition("Running", "Pause", "Paused") self:AddTransition("Paused", "Unpause", "Running") - self:AddTransition("*", "ChangePhase", "*") + self:AddTransition("*", "PhaseOver", "*") self:AddTransition("*", "PhaseChange", "*") self:AddTransition("*", "Over", "Over") @@ -159,6 +175,43 @@ function OPERATION:New(Name) -- @param #string To To state. -- @param #OPERATION.Phase Phase The new phase. + + --- Triggers the FSM event "PhaseOver". + -- @function [parent=#OPERATION] PhaseOver + -- @param #OPERATION self + -- @param #OPERATION.Phase Phase The phase that is over. + + --- Triggers the FSM event "PhaseOver" after a delay. + -- @function [parent=#OPERATION] __PhaseOver + -- @param #OPERATION self + -- @param #number delay Delay in seconds. + -- @param #OPERATION.Phase Phase The phase that is over. + + --- On after "PhaseOver" event. + -- @function [parent=#OPERATION] OnAfterPhaseOver + -- @param #OPERATION self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #OPERATION.Phase Phase The phase that is over. + + + --- Triggers the FSM event "Over". + -- @function [parent=#OPERATION] Over + -- @param #OPERATION self + + --- Triggers the FSM event "Over" after a delay. + -- @function [parent=#OPERATION] __Over + -- @param #OPERATION self + -- @param #number delay Delay in seconds. + + --- On after "Over" event. + -- @function [parent=#OPERATION] OnAfterOver + -- @param #OPERATION self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- Init status update. self:__StatusUpdate(-1) @@ -172,9 +225,8 @@ end --- Create a new generic OPERATION object. -- @param #OPERATION self -- @param #string Name Name of the phase. Default "Phase-01" where the last number is a running number. --- @param Core.Condition#CONDITION ConditionOver Condition when the phase is over. -- @return #OPERATION.Phase Phase table object. -function OPERATION:AddPhase(Name, ConditionOver) +function OPERATION:AddPhase(Name) -- Increase phase counter. self.counterPhase=self.counterPhase+1 @@ -182,8 +234,8 @@ function OPERATION:AddPhase(Name, ConditionOver) local phase={} --#OPERATION.Phase phase.uid=self.counterPhase phase.name=Name or string.format("Phase-%02d", self.counterPhase) - phase.conditionOver=ConditionOver or CONDITION:New(Name) - phase.isOver=false + phase.conditionOver=CONDITION:New(Name.." Over") + phase.status=OPERATION.PhaseStatus.PLANNED -- Add phase. table.insert(self.phases, phase) @@ -213,6 +265,7 @@ end -- @return #OPERATION self function OPERATION:AssignCohort(Cohort) + self:T(self.lid..string.format("Assiging Cohort %s to operation", Cohort.name)) self.cohorts[Cohort.name]=Cohort end @@ -227,7 +280,60 @@ function OPERATION:AssignLegion(Legion) end +--- Check if a given legion is assigned to this operation. All cohorts of this legion will be checked. +-- @param #OPERATION self +-- @param Ops.Legion#LEGION Legion The legion to be assigned. +-- @return #boolean If `true`, legion is assigned to this operation. +function OPERATION:IsAssignedLegion(Legion) + local legion=self.legions[Legion.alias] + + if legion then + self:T(self.lid..string.format("Legion %s is assigned to this operation", Legion.alias)) + return true + else + self:T(self.lid..string.format("Legion %s is NOT assigned to this operation", Legion.alias)) + return false + end + +end + +--- Check if a given cohort is assigned to this operation. +-- @param #OPERATION self +-- @param Ops.Cohort#COHORT Cohort The Cohort. +-- @return #boolean If `true`, cohort is assigned to this operation. +function OPERATION:IsAssignedCohort(Cohort) + + local cohort=self.cohorts[Cohort.name] + + if cohort then + self:T(self.lid..string.format("Cohort %s is assigned to this operation", Cohort.name)) + return true + else + self:T(self.lid..string.format("Cohort %s is NOT assigned to this operation", Cohort.name)) + return false + end + + return nil +end + +--- Check if a given cohort or legion is assigned to this operation. +-- @param #OPERATION self +-- @param Wrapper.Object#OBJECT Object The cohort or legion object. +-- @return #boolean If `true`, cohort is assigned to this operation. +function OPERATION:IsAssignedCohortOrLegion(Object) + + local isAssigned=nil + if Object:IsInstanceOf("COHORT") then + isAssigned=self:IsAssignedCohort(Object) + elseif Object:IsInstanceOf("LEGION") then + isAssigned=self:IsAssignedLegion(Object) + else + self:E(self.lid.."ERROR: Unknown Object!") + end + + return isAssigned +end --- Set start and stop time of the operation. -- @param #OPERATION self @@ -265,6 +371,88 @@ function OPERATION:SetTime(ClockStart, ClockStop) return self end +--- Set status of a phase. +-- @param #OPERATION self +-- @param #OPERATION.Phase Phase The phase. +-- @param #string Status New status, *e.g.* `OPERATION.PhaseStatus.OVER`. +-- @return #OPERATION self +function OPERATION:SetPhaseStatus(Phase, Status) + if Phase then + self:T(self.lid..string.format("Phase %s status: %s-->%s"), Phase.status, Status) + Phase.status=Status + end + return self +end + +--- Get status of a phase. +-- @param #OPERATION self +-- @param #OPERATION.Phase Phase The phase. +-- @return #string Phase status, *e.g.* `OPERATION.PhaseStatus.OVER`. +function OPERATION:GetPhaseStatus(Phase) + return Phase.status +end + +--- Set codition when the given phase is over. +-- @param #OPERATION self +-- @param #OPERATION.Phase Phase The phase. +-- @param Core.Condition#CONDITION Condition Condition when the phase is over. +-- @return #OPERATION self +function OPERATION:SetPhaseConditonOver(Phase, Condition) + if Phase then + self:T(self.lid..string.format("Setting phase %s conditon over %s"), Phase.name, Condition and Condition.name or "None") + Phase.conditionOver=Condition + end + return self +end + +--- Add codition function when the given phase is over. Must return a `#boolean`. +-- @param #OPERATION self +-- @param #OPERATION.Phase Phase The phase. +-- @param #function Function Function that needs to be `true`before the phase is over. +-- @param ... Condition function arguments if any. +-- @return #OPERATION self +function OPERATION:AddPhaseConditonOverAll(Phase, Function, ...) + if Phase then + Phase.conditionOver:AddFunctionAll(Function, ...) + end + return self +end + +--- Add codition function when the given phase is over. Must return a `#boolean`. +-- @param #OPERATION self +-- @param #OPERATION.Phase Phase The phase. +-- @param #function Function Function that needs to be `true`before the phase is over. +-- @param ... Condition function arguments if any. +-- @return #OPERATION self +function OPERATION:AddPhaseConditonOverAny(Phase, Function, ...) + if Phase then + Phase.conditionOver:AddFunctionAny(Function, ...) + end + return self +end + + +--- Get codition when the given phase is over. +-- @param #OPERATION self +-- @param #OPERATION.Phase Phase The phase. +-- @return Core.Condition#CONDITION Condition when the phase is over (if any). +function OPERATION:GetPhaseConditonOver(Phase, Condition) + return Phase.conditionOver +end + +--- Get currrently active phase. +-- @param #OPERATION self +-- @param #OPERATION.Phase Phase The phase. +-- @param #string Status New status, e.g. `OPERATION.PhaseStatus.OVER`. +-- @return #OPERATION self +function OPERATION:SetPhaseStatus(Phase, Status) + if Phase then + self:T(self.lid..string.format("Phase \"%s\" status: %s-->%s", Phase.name, Phase.status, Status)) + Phase.status=Status + end + return self +end + --- Get currrently active phase. -- @param #OPERATION self -- @return #OPERATION.Phase Current phase or `nil` if no current phase is active. @@ -272,6 +460,20 @@ function OPERATION:GetPhaseActive() return self.phase end +--- Check if a phase is the currently active one. +-- @param #OPERATION self +-- @param #OPERATION.Phase Phase The phase to check. +-- @return #boolean If `true`, this phase is currently active. +function OPERATION:IsPhaseActive(Phase) + local phase=self:GetPhaseActive() + if phase and phase.uid==Phase.uid then + return true + else + return false + end + return nil +end + --- Get next phase. -- @param #OPERATION self -- @return #OPERATION.Phase Next phase or `nil` if no next phase exists. @@ -280,7 +482,7 @@ function OPERATION:GetPhaseNext() for _,_phase in pairs(self.phases or {}) do local phase=_phase --#OPERATION.Phase - if not phase.isOver then + if phase.status==OPERATION.PhaseStatus.PLANNED then -- Return first phase that is not over. return phase end @@ -292,17 +494,52 @@ end --- Count phases. -- @param #OPERATION self +-- @param #string Status (Optional) Only count phases in a certain status, e.g. `OPERATION.PhaseStatus.PLANNED`. -- @return #number Number of phases -function OPERATION:CountPhases() +function OPERATION:CountPhases(Status) local N=0 - for phasename, phase in pairs(self.phases) do - N=N+1 + for _,_phase in pairs(self.phases) do + local phase=_phase --#OPERATION.Phase + if Status==nil or Status==phase.status then + N=N+1 + end end return N end +--- Check if operation is in FSM state "Planned". +-- @param #OPERATION self +-- @return #boolean If `true`, operation is "Planned". +function OPERATION:IsPlanned() + local is=self:is("Planned") + return is +end + +--- Check if operation is in FSM state "Running". +-- @param #OPERATION self +-- @return #boolean If `true`, operation is "Running". +function OPERATION:IsRunning() + local is=self:is("Running") + return is +end + +--- Check if operation is in FSM state "Paused". +-- @param #OPERATION self +-- @return #boolean If `true`, operation is "Paused". +function OPERATION:IsPaused() + local is=self:is("Paused") + return is +end + +--- Check if operation is in FSM state "Stopped". +-- @param #OPERATION self +-- @return #boolean If `true`, operation is "Stopped". +function OPERATION:IsStopped() + local is=self:is("Stopped") + return is +end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Status Update @@ -315,12 +552,8 @@ end -- @param #string To To state. function OPERATION:onafterStart(From, Event, To) - -- Get - local Phase=self:GetPhaseNext() - - if Phase then - self:PhaseChange(Phase) - end + -- Debug message. + self:T(self.lid..string.format("Starting Operation!")) end @@ -338,22 +571,34 @@ function OPERATION:onafterStatusUpdate(From, Event, To) -- Current FSM state. local fsmstate=self:GetState() + -- Check phases. + if self:IsRunning() then + self:_CheckPhases() + end + -- Current phase. local currphase=self:GetPhaseActive() - local phasename=currphase and currphase.name or "None" - local Nphase=self:CountPhases() + local phaseName="None" + if currphase then + phaseName=currphase.name + end + local NphaseTot=self:CountPhases() + local NphaseAct=self:CountPhases(OPERATION.PhaseStatus.ACTIVE) + local NphasePla=self:CountPhases(OPERATION.PhaseStatus.PLANNED) + local NphaseOvr=self:CountPhases(OPERATION.PhaseStatus.OVER) -- General info. - local text=string.format("State=%s: Phase=%s, Phases=%d", fsmstate, phasename, Nphase) + local text=string.format("State=%s: Phase=%s, Phases=%d [Active=%d, Planned=%d, Over=%d]", fsmstate, phaseName, NphaseTot, NphaseAct, NphasePla, NphaseOvr) self:I(self.lid..text) -- Info on phases. local text="Phases:" for i,_phase in pairs(self.phases) do local phase=_phase --#OPERATION.Phase - text=text..string.format("\n[%d] %s", i, phase.name) + text=text..string.format("\n[%d] %s: status=%s", i, phase.name, tostring(phase.status)) end if text=="Phases:" then text=text.." None" end + self:I(self.lid..text) -- Next status update. self:__StatusUpdate(-30) @@ -363,20 +608,30 @@ end -- FSM Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- On after "ChangePhase" event. +--- On after "PhaseChange" event. -- @param #OPERATION self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param #OPERATION.Phase Phase The new phase. -function OPERATION:onafterChangePhase(From, Event, To, Phase) +function OPERATION:onafterPhaseChange(From, Event, To, Phase) + + -- Previous phase (if any). + local oldphase="None" + if self.phase then + self:SetPhaseStatus(self.phase, OPERATION.PhaseStatus.OVER) + oldphase=self.phase.name + end -- Debug message. - self:T(self.lid..string.format("Changed to phase: %s", Phase.name)) + self:T(self.lid..string.format("Phase change: %s --> %s", oldphase, Phase.name)) -- Set currently active phase. self.phase=Phase + -- Phase is active. + self:SetPhaseStatus(Phase, OPERATION.PhaseStatus.ACTIVE) + end --- On after "Over" event. @@ -389,7 +644,16 @@ function OPERATION:onafterOver(From, Event, To) -- Debug message. self:T(self.lid..string.format("Operation is over!")) - + + -- No active phase. + self.phase=nil + + -- Set all phases to OVER. + for _,_phase in pairs(self.phases) do + local phase=_phase --#OPERATION.Phase + self:SetPhaseStatus(phase, OPERATION.PhaseStatus.OVER) + end + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -403,13 +667,16 @@ function OPERATION:_CheckPhases() -- Currently active phase. local phase=self:GetPhaseActive() - -- Check if active phase is over. - if phase then - phase.isOver=phase.conditionOver:Evaluate() + -- Check if active phase is over if conditon over is defined. + if phase and phase.conditionOver then + local isOver=phase.conditionOver:Evaluate() + if isOver then + self:SetPhaseStatus(phase, OPERATION.PhaseStatus.OVER) + end end -- If no current phase or current phase is over, get next phase. - if phase==nil or (phase and phase.isOver) then + if phase==nil or phase.status==OPERATION.PhaseStatus.OVER then -- Get next phase. local Phase=self:GetPhaseNext() @@ -423,6 +690,7 @@ function OPERATION:_CheckPhases() -- No further phases defined ==> Operation is over. self:Over() + end end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 1fe3c579d..30ee7f641 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -4646,6 +4646,9 @@ function OPSGROUP:AddMission(Mission) -- Add elements. Mission.Nelements=Mission.Nelements+#self.elements + + -- Increase number of groups. + Mission.Ngroups=Mission.Ngroups+1 -- Add mission to queue. table.insert(self.missionqueue, Mission) diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index f74268b9f..8129059c4 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -38,6 +38,7 @@ -- @field #boolean isDestroyed If true, target objects were destroyed. -- @field #table resources Resource list. -- @field #table conditionStart Start condition functions. +-- @field Ops.Operation#OPERATION operation Operation this target is part of. -- @extends Core.Fsm#FSM --- **It is far more important to be able to hit the target than it is to haggle over who makes a weapon or who pulls a trigger** -- Dwight D Eisenhower