mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-08-15 10:47:21 +00:00
* Added GetTargets() #TARGET * Also call Dead() when no targets left over #PLAYERTASKCONTROLLER * Added FSM events for Flaring, Smoking, and Illumination * Added Illumination of targets in menu if it is night * Rename menu parent setting to SetParentMenu(Menu)
1186 lines
37 KiB
Lua
1186 lines
37 KiB
Lua
--- **Ops** - Operation with multiple phases.
|
|
--
|
|
-- ## Main Features:
|
|
--
|
|
-- * Define operation phases
|
|
-- * Define conditions when phases are over
|
|
-- * Dedicate resources to operations
|
|
--
|
|
-- ===
|
|
--
|
|
-- ## Example Missions:
|
|
--
|
|
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20Operation).
|
|
--
|
|
-- ===
|
|
--
|
|
-- ### Author: **funkyfranky**
|
|
--
|
|
-- ===
|
|
-- @module Ops.Operation
|
|
-- @image OPS_Operation.png
|
|
|
|
|
|
--- OPERATION class.
|
|
-- @type OPERATION
|
|
-- @field #string ClassName Name of the class.
|
|
-- @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 targets Targets.
|
|
-- @field #table missions Missions.
|
|
-- @extends Core.Fsm#FSM
|
|
|
|
--- *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,
|
|
branches = {},
|
|
counterPhase = 0,
|
|
counterBranch = 0,
|
|
counterEdge = 0,
|
|
cohorts = {},
|
|
legions = {},
|
|
targets = {},
|
|
missions = {},
|
|
}
|
|
|
|
--- Global mission counter.
|
|
_OPERATIONID=0
|
|
|
|
--- Operation phase.
|
|
-- @type OPERATION.Phase
|
|
-- @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 #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
|
|
-- @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
|
|
OPERATION.version="0.1.0"
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- TODO list
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
-- TODO: Branches?
|
|
-- TODO: "Over" conditions.
|
|
-- DONE: Phases.
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- Constructor
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- Create a new generic OPERATION object.
|
|
-- @param #OPERATION self
|
|
-- @param #string Name Name of the operation. Be creative! Default "Operation-01" where the last number is a running number.
|
|
-- @return #OPERATION self
|
|
function OPERATION:New(Name)
|
|
|
|
-- Inherit everything from FSM class.
|
|
local self=BASE:Inherit(self, FSM:New()) -- #OPERATION
|
|
|
|
-- 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")
|
|
|
|
-- Master branch.
|
|
self.branchMaster=self:AddBranch("Master")
|
|
|
|
-- Set master as active branch.
|
|
self.branchActive=self.branchMaster
|
|
|
|
-- Add FSM transitions.
|
|
-- From State --> Event --> To State
|
|
self:AddTransition("*", "Start", "Running")
|
|
|
|
self:AddTransition("*", "StatusUpdate", "*")
|
|
|
|
self:AddTransition("Running", "Pause", "Paused")
|
|
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")
|
|
|
|
|
|
------------------------
|
|
--- Pseudo Functions ---
|
|
------------------------
|
|
|
|
--- Triggers the FSM event "Start".
|
|
-- @function [parent=#OPERATION] Start
|
|
-- @param #OPERATION self
|
|
|
|
--- Triggers the FSM event "Start" after a delay.
|
|
-- @function [parent=#OPERATION] __Start
|
|
-- @param #OPERATION self
|
|
-- @param #number delay Delay in seconds.
|
|
|
|
|
|
--- Triggers the FSM event "Stop".
|
|
-- @function [parent=#OPERATION] Stop
|
|
-- @param #OPERATION self
|
|
|
|
--- Triggers the FSM event "Stop" after a delay.
|
|
-- @function [parent=#OPERATION] __Stop
|
|
-- @param #OPERATION self
|
|
-- @param #number delay Delay in seconds.
|
|
|
|
|
|
--- Triggers the FSM event "StatusUpdate".
|
|
-- @function [parent=#OPERATION] StatusUpdate
|
|
-- @param #OPERATION self
|
|
|
|
--- Triggers the FSM event "Status" after a delay.
|
|
-- @function [parent=#OPERATION] __StatusUpdate
|
|
-- @param #OPERATION self
|
|
-- @param #number delay Delay in seconds.
|
|
|
|
|
|
--- Triggers the FSM event "PhaseChange".
|
|
-- @function [parent=#OPERATION] PhaseChange
|
|
-- @param #OPERATION self
|
|
-- @param #OPERATION.Phase Phase The new phase.
|
|
|
|
--- Triggers the FSM event "PhaseChange" after a delay.
|
|
-- @function [parent=#OPERATION] __PhaseChange
|
|
-- @param #OPERATION self
|
|
-- @param #number delay Delay in seconds.
|
|
-- @param #OPERATION.Phase Phase The new phase.
|
|
|
|
--- On after "PhaseChange" event.
|
|
-- @function [parent=#OPERATION] OnAfterPhaseChange
|
|
-- @param #OPERATION self
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
-- @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
|
|
-- @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 "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
|
|
|
|
--- 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)
|
|
|
|
return self
|
|
end
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- User API Functions
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- Set verbosity level.
|
|
-- @param #OPERATION self
|
|
-- @param #number VerbosityLevel Level of output (higher=more). Default 0.
|
|
-- @return #OPERATION self
|
|
function OPERATION:SetVerbosity(VerbosityLevel)
|
|
self.verbose=VerbosityLevel or 0
|
|
return self
|
|
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
|
|
|
|
--- 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, Branch)
|
|
|
|
-- Branch.
|
|
Branch=Branch or self.branchMaster
|
|
|
|
-- 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(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 condition 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 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: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 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
|
|
-- @param #string Name
|
|
-- @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.
|
|
-- @return #string Name Name or "None"
|
|
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.
|
|
-- @param #OPERATION.Phase Phase (Optional) The phase in which the mission should be executed. If no phase is given, it will be exectuted ASAP.
|
|
function OPERATION:AddMission(Mission, Phase)
|
|
|
|
Mission.phase=Phase
|
|
Mission.operation=self
|
|
|
|
table.insert(self.missions, Mission)
|
|
|
|
return self
|
|
end
|
|
|
|
--- Add Target to operation.
|
|
-- @param #OPERATION self
|
|
-- @param Ops.Target#TARGET Target The target to add.
|
|
-- @param #OPERATION.Phase Phase (Optional) The phase in which the target should be attacked. If no phase is given, it will be attacked ASAP.
|
|
function OPERATION:AddTarget(Target, Phase)
|
|
|
|
Target.phase=Phase
|
|
Target.operation=self
|
|
|
|
table.insert(self.targets, Target)
|
|
|
|
return self
|
|
end
|
|
|
|
--- Add Targets from operation.
|
|
-- @param #OPERATION self
|
|
-- @param #OPERATION.Phase Phase
|
|
-- @return #table Targets Table of #TARGET objects
|
|
function OPERATION:GetTargets(Phase)
|
|
local N = {}
|
|
for _,_target in pairs(self.targets) do
|
|
local target=_target --Ops.Target#TARGET
|
|
if target:IsAlive() and (Phase==nil or target.phase==Phase) then
|
|
table.insert(N,target)
|
|
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
|
|
|
|
--- Assign cohort to operation.
|
|
-- @param #OPERATION self
|
|
-- @param Ops.Cohort#COHORT Cohort The cohort
|
|
-- @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
|
|
|
|
--- 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
|
|
function OPERATION:AssignLegion(Legion)
|
|
|
|
self.legions[Legion.alias]=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
|
|
|
|
-- Check if legion of this cohort was assigned.
|
|
local Legion=Cohort.legion
|
|
if Legion and self:IsAssignedLegion(Legion) then
|
|
self:T(self.lid..string.format("Legion %s of Cohort %s is assigned to this operation", Legion.alias, Cohort.name))
|
|
return true
|
|
end
|
|
|
|
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
|
|
|
|
--- 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 "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".
|
|
function OPERATION:IsStopped()
|
|
local is=self:is("Stopped")
|
|
return is
|
|
end
|
|
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- Status Update
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- On after "Start" event.
|
|
-- @param #OPERATION self
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
function OPERATION:onafterStart(From, Event, To)
|
|
|
|
-- Debug message.
|
|
self:T(self.lid..string.format("Starting Operation!"))
|
|
return self
|
|
end
|
|
|
|
|
|
--- On after "StatusUpdate" event.
|
|
-- @param #OPERATION self
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
function OPERATION:onafterStatusUpdate(From, Event, To)
|
|
|
|
-- Current abs. mission time.
|
|
local Tnow=timer.getAbsTime()
|
|
|
|
-- 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()
|
|
end
|
|
|
|
-- Debug output.
|
|
if self.verbose>=1 then
|
|
|
|
-- Current phase.
|
|
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 [%s], Phases=%d [Active=%d, Planned=%d, Over=%d]", fsmstate, phaseName, branchName, NphaseTot, NphaseAct, NphasePla, NphaseOvr)
|
|
self:I(self.lid..text)
|
|
|
|
end
|
|
|
|
-- Debug output.
|
|
if self.verbose>=2 then
|
|
|
|
-- Info on phases.
|
|
local text="Phases:"
|
|
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
|
|
if text=="Phases:" then text=text.." None" end
|
|
self:I(self.lid..text)
|
|
|
|
end
|
|
|
|
-- Next status update.
|
|
self:__StatusUpdate(-30)
|
|
|
|
return self
|
|
end
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- FSM Functions
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- On after "PhaseNext" event.
|
|
-- @param #OPERATION self
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
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
|
|
|
|
return self
|
|
end
|
|
|
|
|
|
--- 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: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:I(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)
|
|
|
|
return self
|
|
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
|
|
|
|
return self
|
|
end
|
|
|
|
--- On after "Over" 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: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 _,_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
|
|
|
|
return self
|
|
end
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- Misc (private) Functions
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- Check phases.
|
|
-- @param #OPERATION self
|
|
function OPERATION:_CheckPhases()
|
|
|
|
-- Currently active phase.
|
|
local phase=self:GetPhaseActive()
|
|
|
|
-- 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.status==OPERATION.PhaseStatus.OVER then
|
|
|
|
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)
|
|
|
|
-- 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
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|