diff --git a/Moose Development/Moose/Core/Condition.lua b/Moose Development/Moose/Core/Condition.lua new file mode 100644 index 000000000..d64b8c727 --- /dev/null +++ b/Moose Development/Moose/Core/Condition.lua @@ -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 +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 7d15b28b1..9928e2e59 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -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' ) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 5142b87d1..f03539d27 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -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 diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index 5b144c499..4ffcf6c01 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -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. @@ -1544,7 +1546,17 @@ function COHORT:_MissileCategoryName(categorynumber) cat="other" end return cat -end +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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index 42296babe..3c4c18503 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -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. diff --git a/Moose Development/Moose/Ops/FlightControl.lua b/Moose Development/Moose/Ops/FlightControl.lua index cd0a2305f..039541019 100644 --- a/Moose Development/Moose/Ops/FlightControl.lua +++ b/Moose Development/Moose/Ops/FlightControl.lua @@ -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 @@ -1832,22 +1834,24 @@ 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 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 @@ -2158,6 +2170,9 @@ 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) @@ -2165,20 +2180,17 @@ function FLIGHTCONTROL:_PlayerRequestInbound(groupname) -- 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 dist0 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. --[[ diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 8854055a4..65b8ae44e 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -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={} @@ -4183,9 +4170,16 @@ function FLIGHTGROUP:_UpdateMenu(delay) -- Delayed call. self:ScheduleOnce(delay, FLIGHTGROUP._UpdateMenu, self) else + + -- 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 group. - local position=self:GetCoordinate() + -- 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 diff --git a/Moose Development/Moose/Ops/Operation.lua b/Moose Development/Moose/Ops/Operation.lua new file mode 100644 index 000000000..d1ea5aa83 --- /dev/null +++ b/Moose Development/Moose/Ops/Operation.lua @@ -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 + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index f76cf8f89..1fe3c579d 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -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 diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 0523f12be..930f2cb06 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -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