- Added new **CONDITION** class
- Added new **OPERATION** class
This commit is contained in:
Frank 2022-05-30 11:45:56 +02:00
parent ae54cd8fde
commit edbfa9117d
10 changed files with 910 additions and 89 deletions

View File

@ -0,0 +1,279 @@
--- **Core** - Define any or all conditions to be evaluated.
--
-- **Main Features:**
--
-- * Add arbitrary numbers of conditon functions
-- * Evaluate *any* or *all* conditions
--
-- ===
--
-- ### Author: **funkyfranky**
-- @module Core.Condition
-- @image Core_Condition.png
--- CONDITON class.
-- @type CONDITION
-- @field #string ClassName Name of the class.
-- @field #string lid Class id string for output to DCS log file.
-- @field #boolean isAny General functions are evaluated as any condition.
-- @field #boolean negateResult Negeate result of evaluation.
-- @field #table functionsGen General condition functions.
-- @field #table functionsAny Any condition functions.
-- @field #table functionsAll All condition functions.
--
-- @extends Core.Base#BASE
--- *Better three hours too soon than a minute too late.* - William Shakespeare
--
-- ===
--
-- # The CONDITION Concept
--
--
--
-- @field #CONDITION
CONDITION = {
ClassName = "CONDITION",
lid = nil,
functionsGen = {},
functionsAny = {},
functionsAll = {},
}
--- Condition function.
-- @type CONDITION.Function
-- @field #function func Callback function to check for a condition. Should return a `#boolean`.
-- @field #table arg (Optional) Arguments passed to the condition callback function if any.
--- CONDITION class version.
-- @field #string version
CONDITION.version="0.0.1"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Make FSM.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new CONDITION object.
-- @param #CONDITION self
-- @param #string Name (Optional) Name used in the logs.
-- @return #CONDITION self
function CONDITION:New(Name)
-- Inherit BASE.
local self=BASE:Inherit(self, BASE:New()) --#CONDITION
self.name=Name or "Condition X"
self.lid=string.format("%s | ", self.name)
return self
end
--- Set that general condition functions return `true` if `any` function returns `true`. Default is that *all* functions must return `true`.
-- @param #CONDITION self
-- @param #boolean Any If `true`, *any* condition can be true. Else *all* conditions must result `true`.
-- @return #CONDITION self
function CONDITION:SetAny(Any)
self.isAny=Any
return self
end
--- Negate result.
-- @param #CONDITION self
-- @param #boolean Negate If `true`, result is negated else not.
-- @return #CONDITION self
function CONDITION:SetNegateResult(Negate)
self.negateResult=Negate
return self
end
--- Add a function that is evaluated. It must return a `#boolean` value, *i.e.* either `true` or `false` (or `nil`).
-- @param #CONDITION self
-- @param #function Function The function to call.
-- @param ... (Optional) Parameters passed to the function (if any).
--
-- @usage
-- local function isAequalB(a, b)
-- return a==b
-- end
--
-- myCondition:AddFunction(isAequalB, a, b)
--
-- @return #CONDITION self
function CONDITION:AddFunction(Function, ...)
-- Condition function.
local condition=self:_CreateCondition(Function, ...)
-- Add to table.
table.insert(self.functionsGen, condition)
return self
end
--- Add a function that is evaluated. It must return a `#boolean` value, *i.e.* either `true` or `false` (or `nil`).
-- @param #CONDITION self
-- @param #function Function The function to call.
-- @param ... (Optional) Parameters passed to the function (if any).
-- @return #CONDITION self
function CONDITION:AddFunctionAny(Function, ...)
-- Condition function.
local condition=self:_CreateCondition(Function, ...)
-- Add to table.
table.insert(self.functionsAny, condition)
return self
end
--- Add a function that is evaluated. It must return a `#boolean` value, *i.e.* either `true` or `false` (or `nil`).
-- @param #CONDITION self
-- @param #function Function The function to call.
-- @param ... (Optional) Parameters passed to the function (if any).
-- @return #CONDITION self
function CONDITION:AddFunctionAll(Function, ...)
-- Condition function.
local condition=self:_CreateCondition(Function, ...)
-- Add to table.
table.insert(self.functionsAll, condition)
return self
end
--- Evaluate conditon functions.
-- @param #CONDITION self
-- @param #boolean AnyTrue If `true`, evaluation return `true` if *any* condition function returns `true`. By default, *all* condition functions must return true.
-- @return #boolean Result of condition functions.
function CONDITION:Evaluate(AnyTrue)
-- Any condition for gen.
local evalAny=self.isAny
if AnyTrue~=nil then
evalAny=AnyTrue
end
local isGen=nil
if evalAny then
isGen=self:_EvalConditionsAny(self.functionsGen)
else
isGen=self:_EvalConditionsAll(self.functionsGen)
end
-- Is any?
local isAny=self:_EvalConditionsAny(self.functionsAny)
-- Is all?
local isAll=self:_EvalConditionsAll(self.functionsAll)
-- Result.
local result=isGen and isAny and isAll
-- Negate result.
if self.negateResult then
result=not result
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)))
return result
end
--- Check if all given condition are true.
-- @param #CONDITION self
-- @param #table functions Functions to evaluate.
-- @return #boolean If true, all conditions were true (or functions was empty/nil). Returns false if at least one condition returned false.
function CONDITION:_EvalConditionsAll(functions)
-- At least one condition?
local gotone=false
-- Any stop condition must be true.
for _,_condition in pairs(functions or {}) do
local condition=_condition --#CONDITION.Function
-- At least one condition was defined.
gotone=true
-- Call function.
local istrue=condition.func(unpack(condition.arg))
-- Any false will return false.
if not istrue then
return false
end
end
-- All conditions were true.
return true
end
--- Check if any of the given conditions is true.
-- @param #CONDITION self
-- @param #table functions Functions to evaluate.
-- @return #boolean If true, at least one condition is true (or functions was emtpy/nil).
function CONDITION:_EvalConditionsAny(functions)
-- At least one condition?
local gotone=false
-- Any stop condition must be true.
for _,_condition in pairs(functions or {}) do
local condition=_condition --#CONDITION.Function
-- At least one condition was defined.
gotone=true
-- Call function.
local istrue=condition.func(unpack(condition.arg))
-- Any true will return true.
if istrue then
return true
end
end
-- No condition was true.
if gotone then
return false
else
-- No functions passed.
return true
end
end
--- Create conditon fucntion object.
-- @param #CONDITION self
-- @param #function Function The function to call.
-- @param ... (Optional) Parameters passed to the function (if any).
-- @return #CONDITION.Function Condition function.
function CONDITION:_CreateCondition(Function, ...)
local condition={} --#CONDITION.Function
condition.func=Function
condition.arg={}
if arg then
condition.arg=arg
end
return condition
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@ -31,6 +31,7 @@ __Moose.Include( 'Scripts/Moose/Core/Spot.lua' )
__Moose.Include( 'Scripts/Moose/Core/Astar.lua' )
__Moose.Include( 'Scripts/Moose/Core/MarkerOps_Base.lua' )
__Moose.Include( 'Scripts/Moose/Core/TextAndSound.lua' )
__Moose.Include( 'Scripts/Moose/Core/Condition.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Object.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Identifiable.lua' )
@ -101,6 +102,7 @@ __Moose.Include( 'Scripts/Moose/Ops/Chief.lua' )
__Moose.Include( 'Scripts/Moose/Ops/Flotilla.lua' )
__Moose.Include( 'Scripts/Moose/Ops/Fleet.lua' )
__Moose.Include( 'Scripts/Moose/Ops/Awacs.lua' )
__Moose.Include( 'Scripts/Moose/Ops/Operation.lua' )
__Moose.Include( 'Scripts/Moose/Ops/FlightControl.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' )

View File

@ -4143,7 +4143,7 @@ function AUFTRAG:CheckGroupsDone()
if groupdata then
if not (groupdata.status==AUFTRAG.GroupStatus.DONE or groupdata.status==AUFTRAG.GroupStatus.CANCELLED) then
-- At least this flight is not DONE or CANCELLED.
self:T(self.lid..string.format("CheckGroupsDone: OPSGROUP %s is not DONE or CANCELLED but in state %s. Mission NOT DONE!", groupdata.opsgroup.groupname, groupdata.status))
self:T2(self.lid..string.format("CheckGroupsDone: OPSGROUP %s is not DONE or CANCELLED but in state %s. Mission NOT DONE!", groupdata.opsgroup.groupname, groupdata.status:upper()))
return false
end
end
@ -4155,7 +4155,7 @@ function AUFTRAG:CheckGroupsDone()
local status=self:GetLegionStatus(legion)
if not status==AUFTRAG.Status.CANCELLED then
-- At least one LEGION has not CANCELLED.
self:T(self.lid..string.format("CheckGroupsDone: LEGION %s is not CANCELLED but in state %s. Mission NOT DONE!", legion.alias, status))
self:T2(self.lid..string.format("CheckGroupsDone: LEGION %s is not CANCELLED but in state %s. Mission NOT DONE!", legion.alias, status))
return false
end
end
@ -4163,7 +4163,7 @@ function AUFTRAG:CheckGroupsDone()
-- Check commander status.
if self.commander then
if not self.statusCommander==AUFTRAG.Status.CANCELLED then
self:T(self.lid..string.format("CheckGroupsDone: COMMANDER is not CANCELLED but in state %s. Mission NOT DONE!", self.statusCommander))
self:T2(self.lid..string.format("CheckGroupsDone: COMMANDER is not CANCELLED but in state %s. Mission NOT DONE!", self.statusCommander))
return false
end
end
@ -4171,14 +4171,14 @@ function AUFTRAG:CheckGroupsDone()
-- Check chief status.
if self.chief then
if not self.statusChief==AUFTRAG.Status.CANCELLED then
self:T(self.lid..string.format("CheckGroupsDone: CHIEF is not CANCELLED but in state %s. Mission NOT DONE!", self.statusChief))
self:T2(self.lid..string.format("CheckGroupsDone: CHIEF is not CANCELLED but in state %s. Mission NOT DONE!", self.statusChief))
return false
end
end
-- These are early stages, where we might not even have a opsgroup defined to be checked. If there were any groups, we checked above.
if self:IsPlanned() or self:IsQueued() or self:IsRequested() then
self:T(self.lid..string.format("CheckGroupsDone: Mission is still in state %s [FSM=%s] (PLANNED or QUEUED or REQUESTED). Mission NOT DONE!", self.status, self:GetState()))
self:T2(self.lid..string.format("CheckGroupsDone: Mission is still in state %s [FSM=%s] (PLANNED or QUEUED or REQUESTED). Mission NOT DONE!", self.status, self:GetState()))
return false
end

View File

@ -48,6 +48,7 @@
-- @field #table tacanChannel List of TACAN channels available to the cohort.
-- @field #number weightAsset Weight of one assets group in kg.
-- @field #number cargobayLimit Cargo bay capacity in kg.
-- @field #table operations Operations this cohort is part of.
-- @extends Core.Fsm#FSM
--- *I came, I saw, I conquered.* -- Julius Caesar
@ -82,6 +83,7 @@ COHORT = {
cargobayLimit = 0,
descriptors = {},
properties = {},
operations = {},
}
--- COHORT class version.
@ -1546,6 +1548,16 @@ function COHORT:_MissileCategoryName(categorynumber)
return cat
end
--- Add an OPERATION.
-- @param #COHORT self
-- @param Ops.Operation#OPERATION Operation The operation this cohort is part of.
-- @return #COHORT self
function COHORT:_AddOperation(Operation)
self.operations[Operation.name]=Operation
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@ -25,6 +25,7 @@
-- @field #table missionqueue Mission queue.
-- @field #table transportqueue Transport queue.
-- @field #table targetqueue Target queue.
-- @field #table opsqueue Operations queue.
-- @field #table rearmingZones Rearming zones. Each element is of type `#BRIGADE.SupplyZone`.
-- @field #table refuellingZones Refuelling zones. Each element is of type `#BRIGADE.SupplyZone`.
-- @field #table capZones CAP zones. Each element is of type `#AIRWING.PatrolZone`.
@ -127,6 +128,7 @@ COMMANDER = {
missionqueue = {},
transportqueue = {},
targetqueue = {},
opsqueue = {},
rearmingZones = {},
refuellingZones = {},
capZones = {},
@ -841,6 +843,9 @@ function COMMANDER:onafterStatus(From, Event, To)
self:T(self.lid..text)
end
-- Check Operations queue.
self:CheckOpsQueue()
-- Check target queue and add missions.
self:CheckTargetQueue()
@ -1217,6 +1222,28 @@ end
-- Mission Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Check OPERATIONs queue.
-- @param #COMMANDER self
function COMMANDER:CheckOpsQueue()
-- Number of missions.
local Nops=#self.opsqueue
-- Treat special cases.
if Nops==0 then
return nil
end
-- Loop over operations.
for _,_ops in pairs(self.opsqueue) do
local operation=_ops --Ops.Operation#OPRATION
--TODO: What?
end
end
--- Check target queue and assign ONE valid target by adding it to the mission queue of the COMMANDER.
-- @param #COMMANDER self
function COMMANDER:CheckTargetQueue()
@ -1281,7 +1308,7 @@ function COMMANDER:CheckTargetQueue()
local isReadyStart=target:EvalConditionsAll(target.conditionStart)
-- Debug message.
local text=string.format("Target %s: Alive=%s, Threat=%s, Important=%s", target:GetName(), tostring(isAlive), tostring(isThreat), tostring(isImportant))
local text=string.format("Target %s: Alive=%s, Important=%s", target:GetName(), tostring(isAlive), tostring(isImportant))
self:T2(self.lid..text)
-- Check that target is alive and not already a mission has been assigned.

View File

@ -149,13 +149,13 @@ FLIGHTCONTROL = {
FLIGHTCONTROL.FlightStatus={
PARKING="Parking",
READYTX="Ready To Taxi",
TAXIOUT="Taxi to runway",
TAXIOUT="Taxi To Runway",
READYTO="Ready For Takeoff",
TAKEOFF="Takeoff",
INBOUND="Inbound",
HOLDING="Holding",
LANDING="Landing",
TAXIINB="Taxi Inbound",
TAXIINB="Taxi To Parking",
ARRIVED="Arrived",
}
@ -172,21 +172,20 @@ FLIGHTCONTROL.version="0.5.2"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
--
-- TODO: Runway destroyed.
-- TODO: Support airwings. Dont give clearance for Alert5 or if mission has not started.
-- TODO: Switch to enable/disable AI messages.
-- TODO: Improve ATC TTS messages.
-- TODO: Add helos.
-- TODO: Talk me down option.
-- TODO: ATIS option.
-- TODO: Check runways and clean up.
-- TODO: Accept and forbit parking spots.
-- TODO: Define holding zone.
-- TODO: Add FARPS?
-- DONE: Define holding zone.
-- DONE: Basic ATC voice overs.
-- DONE: Add SRS TTS.
-- DONE: Add parking guard.
-- NOGO: Add FARPS?
-- DONE: Interface with FLIGHTGROUP.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -793,7 +792,7 @@ function FLIGHTCONTROL:_CheckQueues()
else
-- TODO: Humans have to confirm via F10 menu.
self:SetFlightStatus(flight, FLIGHTCONTROL.FlightStatus.LANDING)
flight:_UpdateMenu()
flight:_UpdateMenu(0.5)
end
-- Set time last flight got landing clearance.
@ -1299,7 +1298,7 @@ function FLIGHTCONTROL:_RemoveFlightFromQueue(queue, flight, queuename)
table.remove(queue, i)
if not flight.isAI then
flight:_UpdateMenu()
flight:_UpdateMenu(0.5)
end
return true, i
@ -1797,6 +1796,9 @@ function FLIGHTCONTROL:_CreatePlayerMenu(flight, mainmenu)
MENU_GROUP_COMMAND:New(group, "Radio Check", helpmenu, self._PlayerRadioCheck, self, groupname)
MENU_GROUP_COMMAND:New(group, "Confirm Status", helpmenu, self._PlayerConfirmStatus, self, groupname)
MENU_GROUP_COMMAND:New(group, "Mark Holding", helpmenu, self._PlayerNotImplemented, self, groupname)
if gotcontrol and flight:IsInbound() and flight.stack then
MENU_GROUP_COMMAND:New(group, "Vector Holding", helpmenu, self._PlayerVectorInbound, self, groupname)
end
---
-- Info Menu
@ -1833,21 +1835,23 @@ function FLIGHTCONTROL:_CreatePlayerMenu(flight, mainmenu)
-- Taxiing
---
if status==FLIGHTCONTROL.FlightStatus.READYTO then
if status==FLIGHTCONTROL.FlightStatus.READYTX or status==FLIGHTCONTROL.FlightStatus.TAXIOUT then
-- Flight is "ready to taxi" (awaiting clearance) or "taxiing to runway".
MENU_GROUP_COMMAND:New(group, "Request Takeoff", rootmenu, self._PlayerRequestTakeoff, self, groupname)
MENU_GROUP_COMMAND:New(group, "Abort Taxi", rootmenu, self._PlayerAbortTaxi, self, groupname)
elseif status==FLIGHTCONTROL.FlightStatus.READYTO then
-- Flight is ready for take off.
MENU_GROUP_COMMAND:New(group, "Abort Takeoff", rootmenu, self._PlayerAbortTakeoff, self, groupname)
elseif status==FLIGHTCONTROL.FlightStatus.TAKEOFF then
-- Flight is taking off.
MENU_GROUP_COMMAND:New(group, "Abort Takeoff", rootmenu, self._PlayerAbortTakeoff, self, groupname)
elseif status==FLIGHTCONTROL.FlightStatus.READYTX or status==FLIGHTCONTROL.FlightStatus.TAXIOUT then
MENU_GROUP_COMMAND:New(group, "Abort Taxi", rootmenu, self._PlayerAbortTaxi, self, groupname)
MENU_GROUP_COMMAND:New(group, "Request Takeoff", rootmenu, self._PlayerRequestTakeoff, self, groupname)
elseif status==FLIGHTCONTROL.FlightStatus.TAXIINB then
-- Could be after "abort taxi" call and we changed our mind (again)
MENU_GROUP_COMMAND:New(group, "Request Parking", rootmenu, self._PlayerRequestParking, self, groupname)
MENU_GROUP_COMMAND:New(group, "Request Taxi", rootmenu, self._PlayerRequestTaxi, self, groupname)
MENU_GROUP_COMMAND:New(group, "Request Parking", rootmenu, self._PlayerRequestParking, self, groupname)
MENU_GROUP_COMMAND:New(group, "Arrived at Parking", rootmenu, self._PlayerArrived, self, groupname)
end
MENU_GROUP_COMMAND:New(group, "Arrived and Parking", rootmenu, self._PlayerArrived, self, groupname)
elseif flight:IsAirborne() then
---
-- Airborne
@ -1858,16 +1862,19 @@ function FLIGHTCONTROL:_CreatePlayerMenu(flight, mainmenu)
-- Inbound
---
MENU_GROUP_COMMAND:New(group, "Abort Inbound", rootmenu, self._PlayerAbortInbound, self, groupname)
MENU_GROUP_COMMAND:New(group, "Holding", rootmenu, self._PlayerHolding, self, groupname)
MENU_GROUP_COMMAND:New(group, "Abort Inbound", rootmenu, self._PlayerAbortInbound, self, groupname)
MENU_GROUP_COMMAND:New(group, "Request Parking", rootmenu, self._PlayerRequestParking, self, groupname)
elseif flight:IsHolding() then
---
-- Holding
---
MENU_GROUP_COMMAND:New(group, "Abort Holding", rootmenu, self._PlayerAbortHolding, self, groupname)
MENU_GROUP_COMMAND:New(group, "Landing", rootmenu, self._PlayerConfirmLanding, self, groupname)
MENU_GROUP_COMMAND:New(group, "Abort Holding", rootmenu, self._PlayerAbortHolding, self, groupname)
MENU_GROUP_COMMAND:New(group, "Request Parking", rootmenu, self._PlayerRequestParking, self, groupname)
elseif flight:IsLanding() then
---
@ -1875,11 +1882,16 @@ function FLIGHTCONTROL:_CreatePlayerMenu(flight, mainmenu)
---
MENU_GROUP_COMMAND:New(group, "Abort Landing", rootmenu, self._PlayerAbortLanding, self, groupname)
end
if flight:IsInbound() or flight:IsHolding() or flight:IsLanding() or flight:IsLanded() then
MENU_GROUP_COMMAND:New(group, "Request Parking", rootmenu, self._PlayerRequestParking, self, groupname)
elseif flight:IsLanded() then
---
-- Landed
---
MENU_GROUP_COMMAND:New(group, "Arrived at Parking", rootmenu, self._PlayerArrived, self, groupname)
MENU_GROUP_COMMAND:New(group, "Request Parking", rootmenu, self._PlayerRequestParking, self, groupname)
end
else
@ -2159,26 +2171,26 @@ function FLIGHTCONTROL:_PlayerRequestInbound(groupname)
-- Call sign.
local callsign=flight:GetCallsignName()
-- Get player element.
local player=flight:GetPlayerElement()
-- Pilot calls inbound for landing.
local text=string.format("%s, %s, inbound for landing", self.alias, callsign)
-- Radio message.
self:TransmissionPilot(text, flight)
-- Current player coord.
local flightcoord=flight:GetCoordinate(nil, player.name)
-- Distance from player to airbase.
local dist=flight:GetCoordinate():Get2DDistance(self:GetCoordinate())
local dist=flightcoord:Get2DDistance(self:GetCoordinate())
if dist<UTILS.NMToMeters(50) then
-- Call RTB event.
-- Call RTB event. This also sets the flight control and flight status to INBOUND and updates the menu.
flight:RTB(self.airbase)
-- Set flightcontrol for this flight.
flight:SetFlightControl(self)
-- Add flight to inbound queue.
self:SetFlightStatus(flight, FLIGHTCONTROL.FlightStatus.INBOUND)
-- Get holding point.
local stack=self:_GetHoldingStack(flight)
@ -2190,14 +2202,11 @@ function FLIGHTCONTROL:_PlayerRequestInbound(groupname)
-- Stack.
flight.stack=stack
-- Current postion.
local coord=flight:GetCoordinate()
-- Heading to holding point.
local heading=coord:HeadingTo(stack.pos0)
local heading=flightcoord:HeadingTo(stack.pos0)
-- Distance to holding point.
local distance=coord:Get2DDistance(stack.pos0)
local distance=flightcoord:Get2DDistance(stack.pos0)
local dist=UTILS.MetersToNM(distance)
@ -2209,7 +2218,7 @@ function FLIGHTCONTROL:_PlayerRequestInbound(groupname)
self:TransmissionTower(text, flight, 15)
-- Create player menu.
flight:_UpdateMenu()
--flight:_UpdateMenu()
else
self:E(self.lid..string.format("WARNING: Could not get holding stack for flight %s", flight:GetName()))
@ -2239,6 +2248,56 @@ function FLIGHTCONTROL:_PlayerRequestInbound(groupname)
end
--- Player vector to inbound
-- @param #FLIGHTCONTROL self
-- @param #string groupname Name of the flight group.
function FLIGHTCONTROL:_PlayerVectorInbound(groupname)
-- Get flight group.
local flight=_DATABASE:GetOpsGroup(groupname) --Ops.FlightGroup#FLIGHTGROUP
if flight then
if flight:IsInbound() and self:IsControlling(flight) and flight.stack then
-- Call sign.
local callsign=flight:GetCallsignName()
-- Get player element.
local player=flight:GetPlayerElement()
-- Current player coord.
local flightcoord=flight:GetCoordinate(nil, player.name)
-- Distance from player to airbase.
local dist=flightcoord:Get2DDistance(self:GetCoordinate())
-- Call sign.
local callsign=flight:GetCallsignName()
-- Heading to holding point.
local heading=flightcoord:HeadingTo(flight.stack.pos0)
-- Distance to holding point.
local distance=flightcoord:Get2DDistance(flight.stack.pos0)
local dist=UTILS.MetersToNM(distance)
-- Message text.
local text=string.format("%s, fly heading %03d for %d nautical miles, hold at angels %d.",
callsign, self.alias, heading, dist, flight.stack.angels)
-- Send message.
self:TextMessageToFlight(text, flight)
end
else
self:E(self.lid..string.format("Cannot find flight group %s.", tostring(groupname)))
end
end
--- Player aborts inbound.
-- @param #FLIGHTCONTROL self
-- @param #string groupname Name of the flight group.
@ -2274,11 +2333,14 @@ function FLIGHTCONTROL:_PlayerAbortInbound(groupname)
self:E(self.lid.."ERROR: No stack!")
end
-- Remove flight. This also updates the menu.
self:_RemoveFlight(flight)
-- Set flight to cruise.
flight:Cruise()
-- Remove flight. This also updates the menu.
self:_RemoveFlight(flight)
-- Create player menu.
--flight:_UpdateMenu()
else
@ -2313,19 +2375,27 @@ function FLIGHTCONTROL:_PlayerHolding(groupname)
if self:IsControlling(flight) then
-- Callsign.
local callsign=flight:GetCallsignName()
-- Player element.
local player=flight:GetPlayerElement()
-- Holding stack.
local stack=flight.stack
if stack then
local Coordinate=flight:GetCoordinate()
-- Current coordinate.
local Coordinate=flight:GetCoordinate(nil, player.name)
-- Distance.
local dist=stack.pos0:Get2DDistance(Coordinate)
if dist<5000 then
-- Message to flight
local text=string.format("Roger, you are added to the holding queue!")
local text=string.format("%s, roger, you are added to the holding queue!", callsign)
self:TextMessageToFlight(text, flight, 10, true)
-- Call holding event.
@ -2396,7 +2466,7 @@ function FLIGHTCONTROL:_PlayerAbortHolding(groupname)
-- Not holding any more.
flight.Tholding=nil
-- Set flight to cruise.
-- Set flight to cruise. This also updates the menu.
flight:Cruise()
-- Set flight.
@ -2471,8 +2541,7 @@ function FLIGHTCONTROL:_PlayerConfirmLanding(groupname)
self:TransmissionTower(text, flight, 10)
-- Create player menu.
flight:_UpdateMenu()
--self:_CreatePlayerMenu(flight, flight.menu.atc)
flight:_UpdateMenu(0.5)
else
@ -2577,7 +2646,7 @@ function FLIGHTCONTROL:_PlayerRequestTaxi(groupname)
self:SetFlightStatus(flight, FLIGHTCONTROL.FlightStatus.READYTX)
-- Update menu.
flight:_UpdateMenu()
flight:_UpdateMenu(0.5)
else
self:TextMessageToFlight(string.format("Negative, you must be PARKING to request TAXI!"), flight)
@ -2616,7 +2685,7 @@ function FLIGHTCONTROL:_PlayerAbortTaxi(groupname)
self:SetFlightStatus(flight, FLIGHTCONTROL.FlightStatus.PARKING)
-- Update menu.
flight:_UpdateMenu()
flight:_UpdateMenu(0.5)
elseif flight:IsTaxiing() then
@ -2628,7 +2697,7 @@ function FLIGHTCONTROL:_PlayerAbortTaxi(groupname)
self:SetFlightStatus(flight, FLIGHTCONTROL.FlightStatus.TAXIINB)
-- Update menu.
flight:_UpdateMenu()
flight:_UpdateMenu(0.5)
else
self:TextMessageToFlight(string.format("Negative, you must be PARKING or TAXIING to abort TAXI!"), flight)
@ -2690,15 +2759,15 @@ function FLIGHTCONTROL:_PlayerRequestTakeoff(groupname)
]]
-- We only check for landing flights.
local text=""
local text=string.format("%s, %s, ", callsign, self.alias)
if Nlanding==0 then
text="No current traffic. You are cleared for takeoff."
text=text.."no current traffic. You are cleared for takeoff."
self:SetFlightStatus(flight, FLIGHTCONTROL.FlightStatus.TAKEOFF)
elseif Nlanding>0 then
if Nlanding==1 then
text=string.format("Negative, we got %d flight inbound before it's your turn. Hold position until futher notice.", Nlanding)
text=text..string.format("negative, we got %d flight inbound before it's your turn. Hold position until futher notice.", Nlanding)
else
text=string.format("Negative, we got %d flights inbound. Hold positon until futher notice.", Nlanding)
text=text..string.format("negative, we got %d flights inbound. Hold positon until futher notice.", Nlanding)
end
end
@ -2706,7 +2775,7 @@ function FLIGHTCONTROL:_PlayerRequestTakeoff(groupname)
self:TransmissionTower(text, flight, 10)
-- Update menu.
flight:_UpdateMenu()
flight:_UpdateMenu(0.5)
else
self:TextMessageToFlight(string.format("Negative, you must request TAXI before you can request TAKEOFF!"), flight)
@ -2755,7 +2824,7 @@ function FLIGHTCONTROL:_PlayerAbortTakeoff(groupname)
self:TransmissionTower(text, flight, 10)
-- Update menu.
flight:_UpdateMenu()
flight:_UpdateMenu(0.5)
else
self:TextMessageToFlight("Negative, You are NOT in the takeoff queue", flight)
@ -2857,7 +2926,7 @@ function FLIGHTCONTROL:_PlayerArrived(groupname)
local player=flight:GetPlayerElement()
-- Get current coordinate.
local coord=flight:GetCoordinate(player.name)
local coord=flight:GetCoordinate(nil, player.name)
--Closest parking spot.
local spot=self:GetClosestParkingSpot(coord)
@ -2882,7 +2951,7 @@ function FLIGHTCONTROL:_PlayerArrived(groupname)
self:SetFlightStatus(flight, FLIGHTCONTROL.FlightStatus.PARKING)
-- Create player menu.
flight:_UpdateMenu()
flight:_UpdateMenu(0.5)
-- Create mark on F10 map.
--[[

View File

@ -385,7 +385,7 @@ function FLIGHTGROUP:SetReadyForTakeoff(ReadyTO, Delay)
return self
end
--- Set the FLIGHTCONTROL controlling this flight group.
--- Set the FLIGHTCONTROL controlling this flight group. Also updates the player menu after 0.5 sec.
-- @param #FLIGHTGROUP self
-- @param Ops.FlightControl#FLIGHTCONTROL flightcontrol The FLIGHTCONTROL object.
-- @return #FLIGHTGROUP self
@ -569,7 +569,7 @@ function FLIGHTGROUP:IsParking()
return self:Is("Parking")
end
--- Check if flight is parking.
--- Check if is taxiing to the runway.
-- @param #FLIGHTGROUP self
-- @return #boolean If true, flight is taxiing after engine start up.
function FLIGHTGROUP:IsTaxiing()
@ -1638,10 +1638,10 @@ function FLIGHTGROUP:onafterSpawned(From, Event, To)
self:SetFlightControl(flightcontrol)
else
-- F10 other menu.
self:_UpdateMenu()
self:_UpdateMenu(0.5)
end
else
self:_UpdateMenu()
self:_UpdateMenu(0.5)
end
end
@ -1679,7 +1679,7 @@ function FLIGHTGROUP:onafterParking(From, Event, To)
if flightcontrol then
-- Set FC for this flight
-- Set FC for this flight. This also updates the menu.
self:SetFlightControl(flightcontrol)
if self.flightcontrol then
@ -1779,20 +1779,7 @@ function FLIGHTGROUP:onafterCruise(From, Event, To)
-- AI
---
--[[
if self:IsTransporting() then
if self.cargoTransport and self.cargoTZC and self.cargoTZC.DeployAirbase then
self:LandAtAirbase(self.cargoTZC.DeployAirbase)
end
elseif self:IsPickingup() then
if self.cargoTransport and self.cargoTZC and self.cargoTZC.PickupAirbase then
self:LandAtAirbase(self.cargoTZC.PickupAirbase)
end
else
self:_CheckGroupDone(nil, 120)
end
]]
-- Check group Done.
self:_CheckGroupDone(nil, 120)
else
@ -1801,7 +1788,7 @@ function FLIGHTGROUP:onafterCruise(From, Event, To)
-- CLIENT
---
self:_UpdateMenu(0.1)
--self:_UpdateMenu(0.1)
end
@ -3202,7 +3189,7 @@ function FLIGHTGROUP:_InitGroup(Template)
-- Set callsign. Default is set on spawn if not modified by user.
local callsign=template.units[1].callsign
self:I({callsign=callsign})
--self:I({callsign=callsign})
if type(callsign)=="number" then -- Sometimes callsign is just "101".
local cs=tostring(callsign)
callsign={}
@ -4184,8 +4171,15 @@ function FLIGHTGROUP:_UpdateMenu(delay)
self:ScheduleOnce(delay, FLIGHTGROUP._UpdateMenu, self)
else
-- Get current position of group.
local position=self:GetCoordinate()
-- Message to group.
MESSAGE:New("Updating MENU state="..self:GetState(), 5):ToGroup(self.group)
env.info(self.lid.."updating menu state=")
-- Player element.
local player=self:GetPlayerElement()
-- Get current position of player.
local position=self:GetCoordinate(nil, player.name)
-- Get all FLIGHTCONTROLS
local fc={}
@ -4211,6 +4205,7 @@ function FLIGHTGROUP:_UpdateMenu(delay)
-- Remove all submenus.
self.menu.atc.root:RemoveSubMenus()
-- Create help menu.
self:_CreateMenuAtcHelp(self.menu.atc.root)
-- Max menu entries.
@ -4260,6 +4255,7 @@ function FLIGHTGROUP:_CreateMenuAtcHelp(rootmenu)
---
MENU_GROUP_COMMAND:New(self.group, "Subtitles On/Off", helpmenu, self._MenuNotImplemented, self, groupname)
MENU_GROUP_COMMAND:New(self.group, "My Voice On/Off", helpmenu, self._MenuNotImplemented, self, groupname)
MENU_GROUP_COMMAND:New(self.group, "Update Menu", helpmenu, self._UpdateMenu, self, 0)
MENU_GROUP_COMMAND:New(self.group, "My Status", helpmenu, self._PlayerMyStatus, self, groupname)
end

View File

@ -0,0 +1,434 @@
--- **Ops** - Operation with multiple phases.
--
-- ## Main Features:
--
-- * Define operation phases
-- * 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 #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).
--
-- @extends Core.Fsm#FSM
--- *A warrior's mission is to foster the success of others.* -- Morihei Ueshiba
--
-- ===
--
-- # The OPERATION Concept
--
--
--
-- @field #OPERATION
OPERATION = {
ClassName = "OPERATION",
verbose = 0,
lid = nil,
cohorts = {},
legions = {},
phases = {},
counterPhase = 0,
}
--- 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 #boolean isOver If `true`, phase is over.
--- OPERATION class version.
-- @field #string version
OPERATION.version="0.0.1"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: A lot
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- 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
-- 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")
-- 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("*", "ChangePhase", "*")
self:AddTransition("*", "PhaseChange", "*")
self:AddTransition("*", "Over", "Over")
self:AddTransition("*", "Stop", "Stopped")
------------------------
--- Pseudo Functions ---
------------------------
--- 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 "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 "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.
-- Init status update.
self:__StatusUpdate(-1)
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User API Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- 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)
-- 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=ConditionOver or CONDITION:New(Name)
phase.isOver=false
-- Add phase.
table.insert(self.phases, phase)
return phase
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 _,_phase in pairs(self.phases or {}) do
local phase=_phase --#OPERATION.Phase
if phase.name==Name then
return phase
end
end
return nil
end
--- Assign cohort to operation.
-- @param #OPERATION self
-- @param Ops.Cohort#COHORT Cohort The cohort
-- @return #OPERATION self
function OPERATION:AssignCohort(Cohort)
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
--- 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
--- 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 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 not phase.isOver then
-- Return first phase that is not over.
return phase
end
end
return nil
end
--- Count phases.
-- @param #OPERATION self
-- @return #number Number of phases
function OPERATION:CountPhases()
local N=0
for phasename, phase in pairs(self.phases) do
N=N+1
end
return N
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)
-- Get
local Phase=self:GetPhaseNext()
if Phase then
self:PhaseChange(Phase)
end
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()
-- Current phase.
local currphase=self:GetPhaseActive()
local phasename=currphase and currphase.name or "None"
local Nphase=self:CountPhases()
-- General info.
local text=string.format("State=%s: Phase=%s, Phases=%d", fsmstate, phasename, Nphase)
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)
end
if text=="Phases:" then text=text.." None" end
-- Next status update.
self:__StatusUpdate(-30)
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- FSM Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- On after "ChangePhase" 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)
-- Debug message.
self:T(self.lid..string.format("Changed to phase: %s", Phase.name))
-- Set currently active phase.
self.phase=Phase
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!"))
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Misc Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Check phases.
-- @param #OPERATION self
function OPERATION:_CheckPhases()
-- Currently active phase.
local phase=self:GetPhaseActive()
-- Check if active phase is over.
if phase then
phase.isOver=phase.conditionOver:Evaluate()
end
-- If no current phase or current phase is over, get next phase.
if phase==nil or (phase and phase.isOver) then
-- 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
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@ -11607,7 +11607,7 @@ function OPSGROUP:SetDefaultCallsign(CallsignName, CallsignNumber)
self.callsignDefault.NumberGroup=CallsignNumber or 1
self.callsignDefault.NameSquad=UTILS.GetCallsignName(self.callsign.NumberSquad)
self:I(self.lid..string.format("Default callsign=%s", self.callsignDefault.NameSquad))
--self:I(self.lid..string.format("Default callsign=%s", self.callsignDefault.NameSquad))
return self
end

View File

@ -30,6 +30,7 @@ Core/Timer.lua
Core/Goal.lua
Core/Spot.lua
Core/TextAndSound.lua
Core/Condition.lua
Wrapper/Object.lua
Wrapper/Identifiable.lua
@ -96,6 +97,7 @@ Ops/Chief.lua
Ops/CSAR.lua
Ops/CTLD.lua
Ops/Awacs.lua
Ops/Operation.lua
Ops/FlightControl.lua
AI/AI_Balancer.lua