diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 6a70170ba..e6a5536d5 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -4474,7 +4474,7 @@ function AUFTRAG:onafterCancel(From, Event, To) else -- Debug info. - self:T(self.lid..string.format("No legion, commander or chief. Attached flights will cancel the mission on their own. Will wait for mission DONE before evaluation!")) + self:T(self.lid..string.format("No legion, commander or chief. Attached groups will cancel the mission on their own. Will wait for mission DONE before evaluation!")) -- Loop over all groups. for _,_groupdata in pairs(self.groupdata or {}) do diff --git a/Moose Development/Moose/Ops/Operation.lua b/Moose Development/Moose/Ops/Operation.lua index ff6b33438..a785fa7ed 100644 --- a/Moose Development/Moose/Ops/Operation.lua +++ b/Moose Development/Moose/Ops/Operation.lua @@ -27,33 +27,55 @@ -- @field #number verbose Verbosity level. -- @field #string lid Class id string for output to DCS log file. -- @field #string name Name of the operation. +-- @field Core.Condition#CONDITION conditionStart Start condition. +-- @field Core.Condition#CONDITION conditionStop Stop condition. +-- @field #table branches Branches. +-- @field #OPERATION.Branch branchMaster Master branch. +-- @field #OPERATION.Branch branchActive Active branch. +-- @field #number counterPhase Running number counting the phases. +-- @field #number counterBranch Running number counting the branches. +-- @field #OPERATION.Phase phase Currently active phase (if any). +-- @field #OPERATION.Phase phaseLast The phase that was active before the current one. -- @field #table cohorts Dedicated cohorts. -- @field #table legions Dedicated legions. --- @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. -- @field #table missions Missions. --- -- @extends Core.Fsm#FSM ---- *A warrior's mission is to foster the success of others.* -- Morihei Ueshiba +--- *Before this time tomorrow I shall have gained a peerage, or Westminster Abbey.* -- Horatio Nelson -- -- === -- -- # The OPERATION Concept -- +-- This class allows you to create complex operations, which consist of multiple phases. Conditions can be specified, when a phase is over. If a phase is over, the next phase is started. +-- FSM events can be used to customize code that is executed at each phase. Phases can also switched manually, of course. +-- +-- In the simplest case, adding phases leads to a linear chain. However, you can also create branches to contruct a more tree like structure of phases. You can switch between branches +-- manually or add "edges" with conditions when to switch branches. We are diving a bit into graph theory here. So don't feel embarrassed at all, if you stick to linear chains. +-- +-- # Constructor +-- +-- A new operation can be created with the @{#OPERATION.New}(*Name*) function, where the parameter `Name` is a free to choose string. +-- +-- ## Adding Phases +-- +-- You can add phases with the @{#OPERATION.AddPhase}(*Name*, *Branch*) function. The first parameter `Name` is the name of the phase. The second parameter `Branch` is the branch to which the phase is +-- added. If this is omitted (nil), the phase is added to the default, *i.e.* "master branch". More about adding branches later. +-- +-- -- -- -- @field #OPERATION OPERATION = { ClassName = "OPERATION", verbose = 0, - lid = nil, - cohorts = {}, - legions = {}, - phases = {}, + branches = {}, counterPhase = 0, + counterBranch = 0, + counterEdge = 0, + cohorts = {}, + legions = {}, targets = {}, missions = {}, } @@ -67,6 +89,23 @@ _OPERATIONID=0 -- @field #string name Name of the phase. -- @field Core.Condition#CONDITION conditionOver Conditions when the phase is over. -- @field #string status Phase status. +-- @field #OPERATION.Branch branch The branch this phase belongs to. + +--- Operation branch. +-- @type OPERATION.Branch +-- @field #number uid Unique ID of the branch. +-- @field #string name Name of the branch. +-- @field #table phases Phases of this branch. +-- @field #table edges Edges of this branch. + +--- Operation edge. +-- @type OPERATION.Edge +-- @field #number uid Unique ID of the edge. +-- @field #OPERATION.Branch branchFrom The from branch. +-- @field #OPERATION.Phase phaseFrom The from phase after which to switch. +-- @field #OPERATION.Branch branchTo The branch to switch to. +-- @field #OPERATION.Phase phaseTo The phase to switch to. +-- @field Core.Condition#CONDITION conditionSwitch Conditions when to switch the branch. --- Operation phase. -- @type OPERATION.PhaseStatus @@ -87,7 +126,9 @@ OPERATION.version="0.1.0" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: A lot +-- TODO: Braches? +-- TODO: Over conditions. +-- DONE: Phases. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -116,6 +157,12 @@ function OPERATION:New(Name) -- FMS start state is PLANNED. self:SetStartState("Planned") + + -- Master branch. + self.branchMaster=self:AddBranch("Master") + + -- Set master as active branch. + self.branchActive=self.branchMaster -- Add FSM transitions. -- From State --> Event --> To State @@ -127,8 +174,11 @@ function OPERATION:New(Name) self:AddTransition("Paused", "Unpause", "Running") self:AddTransition("*", "PhaseOver", "*") + self:AddTransition("*", "PhaseNext", "*") self:AddTransition("*", "PhaseChange", "*") + self:AddTransition("*", "BranchSwitch", "*") + self:AddTransition("*", "Over", "Over") self:AddTransition("*", "Stop", "Stopped") @@ -147,6 +197,7 @@ function OPERATION:New(Name) -- @param #OPERATION self -- @param #number delay Delay in seconds. + --- Triggers the FSM event "Stop". -- @function [parent=#OPERATION] Stop -- @param #OPERATION self @@ -156,6 +207,7 @@ function OPERATION:New(Name) -- @param #OPERATION self -- @param #number delay Delay in seconds. + --- Triggers the FSM event "StatusUpdate". -- @function [parent=#OPERATION] StatusUpdate -- @param #OPERATION self @@ -186,6 +238,23 @@ function OPERATION:New(Name) -- @param #OPERATION.Phase Phase The new phase. + --- Triggers the FSM event "PhaseNext". + -- @function [parent=#OPERATION] PhaseNext + -- @param #OPERATION self + + --- Triggers the FSM event "PhaseNext" after a delay. + -- @function [parent=#OPERATION] __PhaseNext + -- @param #OPERATION self + -- @param #number delay Delay in seconds. + + --- On after "PhaseNext" event. + -- @function [parent=#OPERATION] OnAfterPhaseNext + -- @param #OPERATION self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- Triggers the FSM event "PhaseOver". -- @function [parent=#OPERATION] PhaseOver -- @param #OPERATION self @@ -206,6 +275,26 @@ function OPERATION:New(Name) -- @param #OPERATION.Phase Phase The phase that is over. + --- Triggers the FSM event "BranchSwitch". + -- @function [parent=#OPERATION] BranchSwitch + -- @param #OPERATION self + -- @param #OPERATION.Branch Branch The branch that is now active. + + --- Triggers the FSM event "BranchSwitch" after a delay. + -- @function [parent=#OPERATION] __BranchSwitch + -- @param #OPERATION self + -- @param #number delay Delay in seconds. + -- @param #OPERATION.Branch Branch The branch that is now active. + + --- On after "BranchSwitch" event. + -- @function [parent=#OPERATION] OnAfterBranchSwitch + -- @param #OPERATION self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #OPERATION.Branch Branch The branch that is now active. + + --- Triggers the FSM event "Over". -- @function [parent=#OPERATION] Over -- @param #OPERATION self @@ -241,27 +330,385 @@ function OPERATION:SetVerbosity(VerbosityLevel) return self end ---- Create a new generic OPERATION object. +--- Set start and stop time of the operation. +-- @param #OPERATION self +-- @param #string ClockStart Time the mission is started, e.g. "05:00" for 5 am. If specified as a #number, it will be relative (in seconds) to the current mission time. Default is 5 seconds after mission was added. +-- @param #string ClockStop (Optional) Time the mission is stopped, e.g. "13:00" for 1 pm. If mission could not be started at that time, it will be removed from the queue. If specified as a #number it will be relative (in seconds) to the current mission time. +-- @return #OPERATION self +function OPERATION:SetTime(ClockStart, ClockStop) + + -- Current mission time. + local Tnow=timer.getAbsTime() + + -- Set start time. Default in 5 sec. + local Tstart=Tnow+5 + if ClockStart and type(ClockStart)=="number" then + Tstart=Tnow+ClockStart + elseif ClockStart and type(ClockStart)=="string" then + Tstart=UTILS.ClockToSeconds(ClockStart) + end + + -- Set stop time. Default nil. + local Tstop=nil + if ClockStop and type(ClockStop)=="number" then + Tstop=Tnow+ClockStop + elseif ClockStop and type(ClockStop)=="string" then + Tstop=UTILS.ClockToSeconds(ClockStop) + end + + self.Tstart=Tstart + self.Tstop=Tstop + + if Tstop then + self.duration=self.Tstop-self.Tstart + end + + return self +end + +--- Add a new phase to the operation. This is added add the end of all previously added phases (if any). -- @param #OPERATION self -- @param #string Name Name of the phase. Default "Phase-01" where the last number is a running number. +-- @param #OPERATION.Branch Branch The branch to which this phase is added. Default is the master branch. -- @return #OPERATION.Phase Phase table object. -function OPERATION:AddPhase(Name) +function OPERATION:AddPhase(Name, Branch) - -- Increase phase counter. - self.counterPhase=self.counterPhase+1 + -- Branch. + Branch=Branch or self.branchMaster - local phase={} --#OPERATION.Phase - phase.uid=self.counterPhase - phase.name=Name or string.format("Phase-%02d", self.counterPhase) - phase.conditionOver=CONDITION:New(Name.." Over") - phase.status=OPERATION.PhaseStatus.PLANNED + -- Create a new phase. + local phase=self:_CreatePhase(Name) + + -- Branch of phase + phase.branch=Branch + + + -- Debug output. + self:T(self.lid..string.format("Adding phase %s to branch %s", phase.name, Branch.name)) -- Add phase. - table.insert(self.phases, phase) + table.insert(Branch.phases, phase) return phase end +---Insert a new phase after an already defined phase of the operation. +-- @param #OPERATION self +-- @param #OPERATION.Phase PhaseAfter The phase after which the new phase is inserted. +-- @param #string Name Name of the phase. Default "Phase-01" where the last number is a running number. +-- @return #OPERATION.Phase Phase table object. +function OPERATION:InsertPhaseAfter(PhaseAfter, Name) + + for i=1,#self.phases do + local phase=self.phases[i] --#OPERATION.Phase + if PhaseAfter.uid==phase.uid then + + -- Create a new phase. + local phase=self:_CreatePhase(Name) + + + end + end + + return nil +end + + +--- Get a phase by its name. +-- @param #OPERATION self +-- @param #string Name Name of the phase. Default "Phase-01" where the last number is a running number. +-- @return #OPERATION.Phase Phase table object or nil if phase could not be found. +function OPERATION:GetPhaseByName(Name) + + for _,_branch in pairs(self.branches) do + local branch=_branch --#OPERATION.Branch + for _,_phase in pairs(branch.phases or {}) do + local phase=_phase --#OPERATION.Phase + if phase.name==Name then + return phase + end + end + end + + return nil +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 condition 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. +function OPERATION:GetPhaseActive() + return self.phase +end + +--- Get name of a phase. +-- @param #OPERATION self +-- @param #OPERATION.Phase Phase The phase of which the name is returned. Default is the currently active phase. +-- @return #string The name of the phase or "None" if no phase is given or active. +function OPERATION:GetPhaseName(Phase) + + Phase=Phase or self.phase + + if Phase then + return Phase.name + end + + return "None" +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 index of phase. +-- @param #OPERATION self +-- @param #OPERATION.Phase Phase The phase. +-- @return #number The index. +-- @return #OPERATION.Branch The branch. +function OPERATION:GetPhaseIndex(Phase) + + local branch=Phase.branch + + for i,_phase in pairs(branch.phases) do + local phase=_phase --#OPERATION.Phase + if phase.uid==Phase.uid then + return i, branch + end + end + + return nil +end + +--- Get next phase. +-- @param #OPERATION self +-- @param #OPERATION.Branch Branch (Optional) The branch from which the next phase is retrieved. Default is the currently active branch. +-- @param #string PhaseStatus (Optional) Only return a phase, which is in this status. For example, `OPERATION.PhaseStatus.PLANNED` to make sure, the next phase is planned. +-- @return #OPERATION.Phase Next phase or `nil` if no next phase exists. +function OPERATION:GetPhaseNext(Branch, PhaseStatus) + + -- Branch. + Branch=Branch or self:GetBranchActive() + + -- The phases of the branch. + local phases=Branch.phases or {} + + local phase=nil + if self.phase and self.phase.branch.uid==Branch.uid then + phase=self.phase + end + + -- Number of phases. + local N=#phases + + -- Debug message. + self:T(self.lid..string.format("Getting next phase! Branch=%s, Phases=%d, Status=%s", Branch.name, N, tostring(PhaseStatus))) + + if N>0 then + + -- Check if there there is an active phase already. + if phase==nil and PhaseStatus==nil then + return phases[1] + end + + local n=1 + + if phase then + n=self:GetPhaseIndex(phase)+1 + end + + for i=n,N do + local phase=phases[i] --#OPERATION.Phase + + if PhaseStatus==nil or PhaseStatus==phase.status then + return phase + end + + end + + end + + return nil +end + +--- Count phases. +-- @param #OPERATION self +-- @param #string Status (Optional) Only count phases in a certain status, e.g. `OPERATION.PhaseStatus.PLANNED`. +-- @param #OPERATION.Branch (Optional) Branch. +-- @return #number Number of phases +function OPERATION:CountPhases(Status, Branch) + + Branch=Branch or self.branchActive + + local N=0 + for _,_phase in pairs(Branch.phases) do + local phase=_phase --#OPERATION.Phase + if Status==nil or Status==phase.status then + N=N+1 + end + end + + return N +end + + +--- Add a new branch to the operation. +-- @param #OPERATION self +-- @return #OPERATION.Branch Branch table object. +function OPERATION:AddBranch(Name) + + -- Create a new branch. + local branch=self:_CreateBranch(Name) + + -- Add phase. + table.insert(self.branches, branch) + + return branch +end + +--- Get the currently active branch. +-- @param #OPERATION self +-- @return #OPERATION.Branch The active branch. If no branch is active, the master branch is returned. +function OPERATION:GetBranchActive() + return self.branchActive or self.branchMaster +end + +--- Get name of the branch. +-- @param #OPERATION self +-- @param #OPERATION.Branch Branch The branch of which the name is requested. Default is the currently active or master branch. +function OPERATION:GetBranchName(Branch) + Branch=Branch or self:GetBranchActive() + if Branch then + return Branch.name + end + return "None" +end + +--- Add an edge between two branches. +-- @param #OPERATION self +-- @param #OPERATION.Branch BranchTo The branch *to* which to switch. +-- @param #OPERATION.Phase PhaseAfter The phase of the *from* branch *after* which to switch. +-- @param #OPERATION.Phase PhaseNext The phase of the *to* branch *to* which to switch. +-- @param Core.Condition#CONDITION ConditionSwitch (Optional) Condition(s) when to switch the branches. +-- @return #OPERATION.Branch Branch table object. +function OPERATION:AddEdge(BranchTo, PhaseAfter, PhaseNext, ConditionSwitch) + + local edge={} --#OPERATION.Edge + + edge.branchFrom=PhaseAfter and PhaseAfter.branch or self.branchMaster + edge.phaseFrom=PhaseAfter + edge.branchTo=BranchTo + edge.phaseTo=PhaseNext + edge.conditionSwitch=ConditionSwitch or CONDITION:New("Edge") + + table.insert(edge.branchFrom.edges, edge) + + return edge +end + +--- Add condition function to an edge when branches are switched. The function must return a `#boolean`. +-- @param #OPERATION self +-- @param #OPERATION.Edge Edge The edge connecting the two branches. +-- @param #function Function Function that needs to be `true` for switching between the branches. +-- @param ... Condition function arguments if any. +-- @return #OPERATION self +function OPERATION:AddEdgeConditonSwitchAll(Edge, Function, ...) + if Edge then + Edge.conditionSwitch:AddFunctionAll(Function, ...) + end + return self +end + --- Add mission to operation. -- @param #OPERATION self -- @param Ops.Auftrag#AUFTRAG Mission The mission to add. @@ -291,21 +738,22 @@ function OPERATION:AddTarget(Target, Phase) end - ---- Get a phase by its name. +--- Count targets alive. -- @param #OPERATION self --- @param #string Name Name of the phase. Default "Phase-01" where the last number is a running number. --- @return #OPERATION.Phase Phase table object or nil if phase could not be found. -function OPERATION:GetPhaseByName(Name) +-- @param #OPERATION.Phase Phase (Optional) Only count targets set for this phase. +-- @return #number Number of phases +function OPERATION:CountTargets(Phase) - for _,_phase in pairs(self.phases or {}) do - local phase=_phase --#OPERATION.Phase - if phase.name==Name then - return phase + local N=0 + for _,_target in pairs(self.targets) do + local target=_target --Ops.Target#TARGET + + if target:IsAlive() and (Phase==nil or target.phase==Phase) then + N=N+1 end end - return nil + return N end --- Assign cohort to operation. @@ -319,7 +767,7 @@ function OPERATION:AssignCohort(Cohort) end ---- Assign legion to operation. All cohorts of this legion will be assigned and are only available +--- Assign legion to operation. All cohorts of this legion will be assigned and are only available. -- @param #OPERATION self -- @param Ops.Legion#LEGION Legion The legion to be assigned. -- @return #OPERATION self @@ -392,214 +840,6 @@ function OPERATION:IsAssignedCohortOrLegion(Object) return isAssigned end ---- Set start and stop time of the operation. --- @param #OPERATION self --- @param #string ClockStart Time the mission is started, e.g. "05:00" for 5 am. If specified as a #number, it will be relative (in seconds) to the current mission time. Default is 5 seconds after mission was added. --- @param #string ClockStop (Optional) Time the mission is stopped, e.g. "13:00" for 1 pm. If mission could not be started at that time, it will be removed from the queue. If specified as a #number it will be relative (in seconds) to the current mission time. --- @return #OPERATION self -function OPERATION:SetTime(ClockStart, ClockStop) - - -- Current mission time. - local Tnow=timer.getAbsTime() - - -- Set start time. Default in 5 sec. - local Tstart=Tnow+5 - if ClockStart and type(ClockStart)=="number" then - Tstart=Tnow+ClockStart - elseif ClockStart and type(ClockStart)=="string" then - Tstart=UTILS.ClockToSeconds(ClockStart) - end - - -- Set stop time. Default nil. - local Tstop=nil - if ClockStop and type(ClockStop)=="number" then - Tstop=Tnow+ClockStop - elseif ClockStop and type(ClockStop)=="string" then - Tstop=UTILS.ClockToSeconds(ClockStop) - end - - self.Tstart=Tstart - self.Tstop=Tstop - - if Tstop then - self.duration=self.Tstop-self.Tstart - end - - 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. -function OPERATION:GetPhaseActive() - return self.phase -end - ---- Get name of a phase. --- @param #OPERATION self --- @param #OPERATION.Phase Phase The phase of which the name is returned. --- @return #string The name of the phase. -function OPERATION:GetPhaseName(Phase) - - Phase=Phase or self.phase - - if Phase then - return Phase.name - else - return "None" - end - -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. -function OPERATION:GetPhaseNext() - - for _,_phase in pairs(self.phases or {}) do - local phase=_phase --#OPERATION.Phase - - if phase.status==OPERATION.PhaseStatus.PLANNED then - -- Return first phase that is not over. - return phase - end - - end - - return nil -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(Status) - - local N=0 - 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 - ---- Count targets alive. --- @param #OPERATION self --- @param #OPERATION.Phase Phase (Optional) Only count targets set for this phase. --- @return #number Number of phases -function OPERATION:CountTargets(Phase) - - local N=0 - for _,_target in pairs(self.targets) do - local target=_target --Ops.Target#TARGET - - if target:IsAlive() and (Phase==nil or target.phase==Phase) 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". @@ -624,6 +864,14 @@ function OPERATION:IsPaused() return is end +--- Check if operation is in FSM state "Over". +-- @param #OPERATION self +-- @return #boolean If `true`, operation is "Over". +function OPERATION:IsOver() + local is=self:is("Over") + return is +end + --- Check if operation is in FSM state "Stopped". -- @param #OPERATION self -- @return #boolean If `true`, operation is "Stopped". @@ -632,6 +880,7 @@ function OPERATION:IsStopped() return is end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Status Update ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -662,6 +911,22 @@ function OPERATION:onafterStatusUpdate(From, Event, To) -- Current FSM state. local fsmstate=self:GetState() + if self:IsPlanned() then + if self.Tstart and Tnow>self.Tstart then + self:Start() + end + end + if (self.Tstop and Tnow>self.Tstop) and not (self:IsOver() or self:IsStopped()) then + self:Over() + end + + if (not self:IsRunning()) and (self.conditionStart and self.conditionStart:Evaluate()) then + self:Start() + end + if self:IsRunning() and (self.conditionStop and self.conditionStop:Evaluate()) then + self:Over() + end + -- Check phases. if self:IsRunning() then self:_CheckPhases() @@ -671,18 +936,15 @@ function OPERATION:onafterStatusUpdate(From, Event, To) if self.verbose>=1 then -- Current phase. - local currphase=self:GetPhaseActive() - local phaseName="None" - if currphase then - phaseName=currphase.name - end + local phaseName=self:GetPhaseName() + local branchName=self:GetBranchName() 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 [Active=%d, Planned=%d, Over=%d]", fsmstate, phaseName, NphaseTot, NphaseAct, NphasePla, NphaseOvr) + local text=string.format("State=%s: Phase=%s [%s], Phases=%d [Active=%d, Planned=%d, Over=%d]", fsmstate, phaseName, branchName, NphaseTot, NphaseAct, NphasePla, NphaseOvr) self:I(self.lid..text) end @@ -692,7 +954,7 @@ function OPERATION:onafterStatusUpdate(From, Event, To) -- Info on phases. local text="Phases:" - for i,_phase in pairs(self.phases) do + for i,_phase in pairs(self.branchActive.phases) do local phase=_phase --#OPERATION.Phase text=text..string.format("\n[%d] %s: status=%s", i, phase.name, tostring(phase.status)) end @@ -709,6 +971,32 @@ end -- FSM Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- On after "PhaseNext" 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:onafterPhaseNext(From, Event, To) + + -- Get next phase. + local Phase=self:GetPhaseNext() + + if Phase then + + -- Change phase to next one. + self:PhaseChange(Phase) + + else + + -- No further phases defined ==> Operation is over. + self:Over() + + end + +end + + --- On after "PhaseChange" event. -- @param #OPERATION self -- @param #string From From state. @@ -735,6 +1023,22 @@ function OPERATION:onafterPhaseChange(From, Event, To, Phase) end +--- On after "BranchSwitch" event. +-- @param #OPERATION self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #OPERATION.Branch Branch The new branch. +function OPERATION:onafterBranchSwitch(From, Event, To, Branch) + + -- Debug info. + self:T(self.lid..string.format("Switching to branch %s", Branch.name)) + + -- Set active branch. + self.branchActive=Branch + +end + --- On after "Over" event. -- @param #OPERATION self -- @param #string From From state. @@ -750,15 +1054,17 @@ function OPERATION:onafterOver(From, Event, To) 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 - + for _,_branch in pairs(self.branches) do + local branch=_branch --#OPERATION.Branch + for _,_phase in pairs(branch.phases) do + local phase=_phase --#OPERATION.Phase + self:SetPhaseStatus(phase, OPERATION.PhaseStatus.OVER) + end + end end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Misc Functions +-- Misc (private) Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Check phases. @@ -779,25 +1085,79 @@ function OPERATION:_CheckPhases() -- If no current phase or current phase is over, get next phase. if phase==nil or phase.status==OPERATION.PhaseStatus.OVER then - -- Get next phase. - local Phase=self:GetPhaseNext() - - if Phase then - - -- Change phase to next one. - self:PhaseChange(Phase) + for _,_edge in pairs(self.branchActive.edges) do + local edge=_edge --#OPERATION.Edge + + if (edge.phaseFrom==nil) or (phase and edge.phaseFrom.uid==phase.uid) then + + -- Evaluate switch condition. + local switch=edge.conditionSwitch:Evaluate() + + if switch then + + -- Switch to new branch. + self:BranchSwitch(edge.branchTo) + + -- If we want to switch to a specific phase of the branch. + if edge.phaseTo then + + -- Change phase. + self:PhaseChange(edge.phaseTo) - else - - -- No further phases defined ==> Operation is over. - self:Over() + -- Done here! + return + end + + -- Break the loop. + break + end + end end + -- Next phase. + self:PhaseNext() + end end +--- Create a new phase object. +-- @param #OPERATION self +-- @param #string Name Name of the phase. Default "Phase-01" where the last number is a running number. +-- @return #OPERATION.Phase Phase table object. +function OPERATION:_CreatePhase(Name) + + -- Increase phase counter. + self.counterPhase=self.counterPhase+1 + + local phase={} --#OPERATION.Phase + phase.uid=self.counterPhase + phase.name=Name or string.format("Phase-%02d", self.counterPhase) + phase.conditionOver=CONDITION:New(Name.." Over") + phase.status=OPERATION.PhaseStatus.PLANNED + + return phase +end + +--- Create a new branch object. +-- @param #OPERATION self +-- @param #string Name Name of the phase. Default "Phase-01" where the last number is a running number. +-- @return #OPERATION.Branch Branch table object. +function OPERATION:_CreateBranch(Name) + + -- Increase phase counter. + self.counterBranch=self.counterBranch+1 + + local branch={} --#OPERATION.Branch + branch.uid=self.counterBranch + branch.name=Name or string.format("Branch-%02d", self.counterBranch) + branch.phases={} + branch.edges={} + + return branch +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 91f5f80e1..f3f4932bb 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -4711,9 +4711,13 @@ end -- @return #OPSGROUP self function OPSGROUP:RemoveMission(Mission) - for i,_mission in pairs(self.missionqueue) do - local mission=_mission --Ops.Auftrag#AUFTRAG + --for i,_mission in pairs(self.missionqueue) do + for i=#self.missionqueue,1,-1 do + + -- Mission. + local mission=self.missionqueue[i] --Ops.Auftrag#AUFTRAG + -- Check mission ID. if mission.auftragsnummer==Mission.auftragsnummer then -- Remove mission waypoint task. @@ -4724,9 +4728,10 @@ function OPSGROUP:RemoveMission(Mission) end -- Take care of a paused mission. - for j,mid in pairs(self.pausedmissions) do + for j=#self.pausedmissions,1,-1 do + local mid=self.pausedmissions[j] if Mission.auftragsnummer==mid then - table.remove(self.pausedmission, j) + table.remove(self.pausedmissions, j) end end @@ -5161,6 +5166,7 @@ function OPSGROUP:onafterMissionCancel(From, Event, To, Mission) --- -- Some missions dont have a task set, which could be cancelled. + --[[ if Mission.type==AUFTRAG.Type.ALERT5 or Mission.type==AUFTRAG.Type.ONGUARD or Mission.type==AUFTRAG.Type.ARMOREDGUARD or @@ -5173,19 +5179,31 @@ function OPSGROUP:onafterMissionCancel(From, Event, To, Mission) return end + ]] -- Get mission waypoint task. local Task=Mission:GetGroupWaypointTask(self) + + if Task then - -- Debug info. - self:T(self.lid..string.format("Cancel current mission %s. Task=%s", tostring(Mission.name), tostring(Task and Task.description or "WTF"))) + -- Debug info. + self:T(self.lid..string.format("Cancel current mission %s. Task=%s", tostring(Mission.name), tostring(Task and Task.description or "WTF"))) + + -- Cancelling the mission is actually cancelling the current task. + -- Note that two things can happen. + -- 1.) Group is still on the way to the waypoint (status should be STARTED). In this case there would not be a current task! + -- 2.) Group already passed the mission waypoint (status should be EXECUTING). + + self:TaskCancel(Task) + + else + + -- Some missions dont have a task set, which could be cancelled. - -- Cancelling the mission is actually cancelling the current task. - -- Note that two things can happen. - -- 1.) Group is still on the way to the waypoint (status should be STARTED). In this case there would not be a current task! - -- 2.) Group already passed the mission waypoint (status should be EXECUTING). - - self:TaskCancel(Task) + -- Trigger mission don task. + self:MissionDone(Mission) + + end else